From 2e4f31ded88b99950dd5ce067082d6da604591ed Mon Sep 17 00:00:00 2001 From: Kalle Bladin Date: Fri, 14 Jul 2017 17:17:17 +0200 Subject: [PATCH] Interaction Updates (#353) * Interaction speed is not dependent on framerate * Split up interaction code in files and perform smooth interpolation when changing focus * Abstract interaction code in to functions. * Interpolation time is dependent on angle to focus node. * Use correct delta time when interpolating * Fix bug regarding decomposition of camera rotation. * Make orbital interaction mode behave as globe browsing and no longer use interactiondepth below ellipsoid. * Do not always rotate with object. Depending on distance * Remove interaction depth below ellipsoid. Now able to interact without renderable * Remove specification of interactionDepthBelowEllipsoid and cameraMinHeight * Remove GlobeBrowsingInteractionMode * Rename OrbitalInteractionMode to OrbitalNavigator and no longer extend interactionmode. * Move properties from interaction handler to orbital navigator * Use smooth step for follow rotation interpolator * Rename KeyframeInteractionMode to KeyframeNavigator * Rename files * Clean up. * Separate mousestate from orbitalnavigator * Clean up * Split keybindingmanager from interactionhandler interactionhandler * Rename interactionhandler to navigationhandler * Rename files * Clean up * Take back usage of gotochunk and gotogeo * Rename lua library navigation * Move functionality from navigationhandler to keyframenavigator * Update scripts for navigation * Comment code * Clean up * Solve but that caused NaN values for camera position when being in center of globe and setting focus to the globe. * Update jenkins file to remove build folder before building. * Fix error in jenkins script * Update jenkins file * Update jenkins file * Revert jenkins file * I hope this makes Jenkins happy. * Line endings God damnit * Line endings * Clean up * Fix compilation issue * Take back default scene. * Fix indentation * Move functions goToGeo and goToChunk to GlobeBrowsingModule. * Include algorithm for std::find * Remove auto and other clean up --- data/scene/globebrowsing.scene | 4 +- data/scene/lodglobes/earth/earth.mod | 2 - .../lodglobes/jupiter/callisto/callisto.mod | 2 - .../scene/lodglobes/jupiter/europa/europa.mod | 2 - .../lodglobes/jupiter/ganymede/ganymede.mod | 2 - data/scene/lodglobes/jupiter/io/io.mod | 2 - .../lodglobes/jupiter/jupiter/jupiter.mod | 2 - data/scene/lodglobes/mars/mars.mod | 4 - data/scene/lodglobes/moon/moon.mod | 3 - data/scene/lodglobes/neptune/neptune.mod | 2 - data/scene/lodglobes/saturn/saturn.mod | 2 - data/scene/lodglobes/uranus/uranus.mod | 2 - data/scene/lodglobes/venus/venus.mod | 2 - include/openspace/engine/openspaceengine.h | 11 +- include/openspace/interaction/controller.h | 6 +- .../openspace/interaction/delayedvariable.h | 59 ++ .../openspace/interaction/delayedvariable.inl | 76 ++ include/openspace/interaction/inputstate.h | 70 ++ .../openspace/interaction/interactionmode.h | 286 ------- include/openspace/interaction/interpolator.h | 65 ++ .../openspace/interaction/interpolator.inl | 85 ++ .../openspace/interaction/keybindingmanager.h | 87 ++ .../openspace/interaction/keyframenavigator.h | 69 ++ include/openspace/interaction/mousestate.h | 80 ++ ...teractionhandler.h => navigationhandler.h} | 102 +-- .../openspace/interaction/orbitalnavigator.h | 197 +++++ include/openspace/rendering/renderable.h | 3 + include/openspace/scene/scenegraphnode.h | 3 + include/openspace/util/updatestructures.h | 15 + modules/globebrowsing/chunk/chunk.cpp | 2 +- .../globebrowsing/geometry/geodeticpatch.h | 3 +- modules/globebrowsing/globebrowsingmodule.cpp | 178 +++- modules/globebrowsing/globebrowsingmodule.h | 24 +- .../globebrowsing/globebrowsingmodule_lua.inl | 42 + .../globebrowsing/globes/chunkedlodglobe.cpp | 2 +- .../globebrowsing/globes/renderableglobe.cpp | 40 +- .../globebrowsing/globes/renderableglobe.h | 7 +- .../globebrowsing/rendering/chunkrenderer.cpp | 4 +- .../rendering/gpu/gpulayergroup.cpp | 2 +- modules/onscreengui/onscreenguimodule.cpp | 4 +- .../onscreengui/src/guiorigincomponent.cpp | 6 +- .../onscreengui/src/guiparallelcomponent.cpp | 4 +- modules/touch/include/TouchInteraction.h | 4 +- modules/touch/src/TouchInteraction.cpp | 8 +- modules/touch/src/TuioEar.cpp | 4 +- modules/touch/touchmodule.cpp | 6 +- scripts/common.lua | 6 +- src/CMakeLists.txt | 23 +- src/documentation/core_registration.cpp | 6 +- src/engine/openspaceengine.cpp | 55 +- src/interaction/controller.cpp | 4 +- src/interaction/inputstate.cpp | 97 +++ src/interaction/interactionhandler.cpp | 563 ------------ src/interaction/interactionmode.cpp | 800 ------------------ src/interaction/keybindingmanager.cpp | 178 ++++ ...dler_lua.inl => keybindingmanager_lua.inl} | 117 +-- src/interaction/keyframenavigator.cpp | 127 +++ src/interaction/mousestate.cpp | 177 ++++ src/interaction/navigationhandler.cpp | 299 +++++++ src/interaction/navigationhandler_lua.inl | 113 +++ src/interaction/orbitalnavigator.cpp | 532 ++++++++++++ src/network/parallelconnection.cpp | 26 +- src/query/query.cpp | 2 +- src/rendering/renderable.cpp | 11 + src/rendering/renderengine.cpp | 2 +- src/scene/scene.cpp | 2 +- src/scene/scenegraphnode.cpp | 15 + src/util/camera.cpp | 1 - 68 files changed, 2764 insertions(+), 1977 deletions(-) create mode 100644 include/openspace/interaction/delayedvariable.h create mode 100644 include/openspace/interaction/delayedvariable.inl create mode 100644 include/openspace/interaction/inputstate.h delete mode 100644 include/openspace/interaction/interactionmode.h create mode 100644 include/openspace/interaction/interpolator.h create mode 100644 include/openspace/interaction/interpolator.inl create mode 100644 include/openspace/interaction/keybindingmanager.h create mode 100644 include/openspace/interaction/keyframenavigator.h create mode 100644 include/openspace/interaction/mousestate.h rename include/openspace/interaction/{interactionhandler.h => navigationhandler.h} (59%) create mode 100644 include/openspace/interaction/orbitalnavigator.h create mode 100644 src/interaction/inputstate.cpp delete mode 100644 src/interaction/interactionhandler.cpp delete mode 100644 src/interaction/interactionmode.cpp create mode 100644 src/interaction/keybindingmanager.cpp rename src/interaction/{interactionhandler_lua.inl => keybindingmanager_lua.inl} (61%) create mode 100644 src/interaction/keyframenavigator.cpp create mode 100644 src/interaction/mousestate.cpp create mode 100644 src/interaction/navigationhandler.cpp create mode 100644 src/interaction/navigationhandler_lua.inl create mode 100644 src/interaction/orbitalnavigator.cpp diff --git a/data/scene/globebrowsing.scene b/data/scene/globebrowsing.scene index 9e6243d135..0c2b8d23c3 100644 --- a/data/scene/globebrowsing.scene +++ b/data/scene/globebrowsing.scene @@ -78,7 +78,7 @@ function postInitialization() openspace.setPropertyValue("Earth.RenderableGlobe.Debug.levelByProjectedAreaElseDistance", false) openspace.setPropertyValue("Earth.RenderableGlobe.Layers.ColorLayers.blendTileLevels", true) - openspace.resetCameraDirection() + openspace.globebrowsing.goToGeo(0, 0, 20000000) openspace.printInfo("Done setting default values") end @@ -89,7 +89,7 @@ return { CommonFolder = "common", Camera = { Focus = "Earth", - Position = {30000000, 0, 0}, + Position = {0, 0, 0}, Rotation = {0.758797, 0.221490, -0.605693, -0.091135}, }, diff --git a/data/scene/lodglobes/earth/earth.mod b/data/scene/lodglobes/earth/earth.mod index b55e23430f..0819aa8638 100644 --- a/data/scene/lodglobes/earth/earth.mod +++ b/data/scene/lodglobes/earth/earth.mod @@ -63,8 +63,6 @@ return { Renderable = { Type = "RenderableGlobe", Radii = earthEllipsoid, - CameraMinHeight = 300, - InteractionDepthBelowEllipsoid = 0, -- Useful when having negative height map values SegmentsPerPatch = 64, Layers = { ColorLayers = { diff --git a/data/scene/lodglobes/jupiter/callisto/callisto.mod b/data/scene/lodglobes/jupiter/callisto/callisto.mod index 53b8cee865..302fc7a103 100644 --- a/data/scene/lodglobes/jupiter/callisto/callisto.mod +++ b/data/scene/lodglobes/jupiter/callisto/callisto.mod @@ -23,8 +23,6 @@ return { Renderable = { Type = "RenderableGlobe", Radii = {2631000, 2631000, 2631000}, - CameraMinHeight = 300, - InteractionDepthBelowEllipsoid = 0, -- Useful when having negative height map values SegmentsPerPatch = 64, Layers = { ColorLayers = { diff --git a/data/scene/lodglobes/jupiter/europa/europa.mod b/data/scene/lodglobes/jupiter/europa/europa.mod index 720e68d7db..d84d20d48d 100644 --- a/data/scene/lodglobes/jupiter/europa/europa.mod +++ b/data/scene/lodglobes/jupiter/europa/europa.mod @@ -23,8 +23,6 @@ return { Renderable = { Type = "RenderableGlobe", Radii = {1561000, 1561000, 1561000}, - CameraMinHeight = 300, - InteractionDepthBelowEllipsoid = 0, -- Useful when having negative height map values SegmentsPerPatch = 64, Layers = { ColorLayers = { diff --git a/data/scene/lodglobes/jupiter/ganymede/ganymede.mod b/data/scene/lodglobes/jupiter/ganymede/ganymede.mod index 098014cc0c..e37a193bb8 100644 --- a/data/scene/lodglobes/jupiter/ganymede/ganymede.mod +++ b/data/scene/lodglobes/jupiter/ganymede/ganymede.mod @@ -23,8 +23,6 @@ return { Renderable = { Type = "RenderableGlobe", Radii = {2631000, 2631000, 2631000}, - CameraMinHeight = 300, - InteractionDepthBelowEllipsoid = 0, -- Useful when having negative height map values SegmentsPerPatch = 64, Layers = { ColorLayers = { diff --git a/data/scene/lodglobes/jupiter/io/io.mod b/data/scene/lodglobes/jupiter/io/io.mod index dc6b7c2b8e..87dfc06b20 100644 --- a/data/scene/lodglobes/jupiter/io/io.mod +++ b/data/scene/lodglobes/jupiter/io/io.mod @@ -23,8 +23,6 @@ return { Renderable = { Type = "RenderableGlobe", Radii = {1821300, 1821300, 1821300}, - CameraMinHeight = 300, - InteractionDepthBelowEllipsoid = 0, -- Useful when having negative height map values SegmentsPerPatch = 64, Layers = { ColorLayers = { diff --git a/data/scene/lodglobes/jupiter/jupiter/jupiter.mod b/data/scene/lodglobes/jupiter/jupiter/jupiter.mod index 2c6610b3ae..58dc65149d 100644 --- a/data/scene/lodglobes/jupiter/jupiter/jupiter.mod +++ b/data/scene/lodglobes/jupiter/jupiter/jupiter.mod @@ -34,8 +34,6 @@ return { Renderable = { Type = "RenderableGlobe", Radii = {71492000, 71492000, 66854000}, - CameraMinHeight = 300, - InteractionDepthBelowEllipsoid = 0, -- Useful when having negative height map values SegmentsPerPatch = 64, Layers = { ColorLayers = { diff --git a/data/scene/lodglobes/mars/mars.mod b/data/scene/lodglobes/mars/mars.mod index 7443aa8f2f..ceee56e4ea 100644 --- a/data/scene/lodglobes/mars/mars.mod +++ b/data/scene/lodglobes/mars/mars.mod @@ -32,11 +32,7 @@ return { Renderable = { Type = "RenderableGlobe", Radii = marsEllipsoid, - CameraMinHeight = 10, SegmentsPerPatch = 90, - -- Allows camera to go down 10000 meters below the reference ellipsoid - -- Useful when having negative height map values - InteractionDepthBelowEllipsoid = 10000, Layers = { ColorLayers = { { diff --git a/data/scene/lodglobes/moon/moon.mod b/data/scene/lodglobes/moon/moon.mod index 40499d6004..1ddb3cf044 100644 --- a/data/scene/lodglobes/moon/moon.mod +++ b/data/scene/lodglobes/moon/moon.mod @@ -19,10 +19,7 @@ return { Renderable = { Type = "RenderableGlobe", Radii = {1738140, 1738140, 1735970}, -- Moons's radius - CameraMinHeight = 300, SegmentsPerPatch = 64, - -- Allows camera to go down 10000 meters below the reference ellipsoid - InteractionDepthBelowEllipsoid = 10000, -- Useful when having negative height map values Layers = { ColorLayers = { { diff --git a/data/scene/lodglobes/neptune/neptune.mod b/data/scene/lodglobes/neptune/neptune.mod index 31bac3a1fe..99513e18bb 100644 --- a/data/scene/lodglobes/neptune/neptune.mod +++ b/data/scene/lodglobes/neptune/neptune.mod @@ -31,8 +31,6 @@ return { Renderable = { Type = "RenderableGlobe", Radii = {24764000, 24764000, 24314000}, - CameraMinHeight = 300, - InteractionDepthBelowEllipsoid = 0, -- Useful when having negative height map values SegmentsPerPatch = 64, Layers = { ColorLayers = { diff --git a/data/scene/lodglobes/saturn/saturn.mod b/data/scene/lodglobes/saturn/saturn.mod index cb04b02d13..dc1ef691eb 100644 --- a/data/scene/lodglobes/saturn/saturn.mod +++ b/data/scene/lodglobes/saturn/saturn.mod @@ -31,8 +31,6 @@ return { Renderable = { Type = "RenderableGlobe", Radii = {60268000, 60268000, 54364000}, - CameraMinHeight = 300, - InteractionDepthBelowEllipsoid = 0, -- Useful when having negative height map values SegmentsPerPatch = 64, Layers = { ColorLayers = { diff --git a/data/scene/lodglobes/uranus/uranus.mod b/data/scene/lodglobes/uranus/uranus.mod index 81dd7f8eb3..b8cda62a16 100644 --- a/data/scene/lodglobes/uranus/uranus.mod +++ b/data/scene/lodglobes/uranus/uranus.mod @@ -31,8 +31,6 @@ return { Renderable = { Type = "RenderableGlobe", Radii = {25559000, 25559000, 24973000}, - CameraMinHeight = 300, - InteractionDepthBelowEllipsoid = 0, -- Useful when having negative height map values SegmentsPerPatch = 64, Layers = { ColorLayers = { diff --git a/data/scene/lodglobes/venus/venus.mod b/data/scene/lodglobes/venus/venus.mod index 6288883665..285f88f466 100644 --- a/data/scene/lodglobes/venus/venus.mod +++ b/data/scene/lodglobes/venus/venus.mod @@ -36,8 +36,6 @@ return { Renderable = { Type = "RenderableGlobe", Radii = {6051900, 6051900, 6051800}, - CameraMinHeight = 300, - InteractionDepthBelowEllipsoid = 0, -- Useful when having negative height map values SegmentsPerPatch = 64, Layers = { ColorLayers = { diff --git a/include/openspace/engine/openspaceengine.h b/include/openspace/engine/openspaceengine.h index 00de495380..df4c372ee5 100644 --- a/include/openspace/engine/openspaceengine.h +++ b/include/openspace/engine/openspaceengine.h @@ -62,7 +62,10 @@ class SyncEngine; class TimeManager; class WindowWrapper; -namespace interaction { class InteractionHandler; } +namespace interaction { + class NavigationHandler; + class KeyBindingManager; +} namespace gui { class GUI; } namespace properties { class PropertyOwner; } namespace scripting { @@ -123,7 +126,8 @@ public: TimeManager& timeManager(); WindowWrapper& windowWrapper(); ghoul::fontrendering::FontManager& fontManager(); - interaction::InteractionHandler& interactionHandler(); + interaction::NavigationHandler& navigationHandler(); + interaction::KeyBindingManager& keyBindingManager(); properties::PropertyOwner& globalPropertyOwner(); scripting::ScriptEngine& scriptEngine(); scripting::ScriptScheduler& scriptScheduler(); @@ -197,7 +201,8 @@ private: std::unique_ptr _windowWrapper; std::unique_ptr _commandlineParser; std::unique_ptr _fontManager; - std::unique_ptr _interactionHandler; + std::unique_ptr _navigationHandler; + std::unique_ptr _keyBindingManager; std::unique_ptr _scriptEngine; std::unique_ptr _scriptScheduler; std::unique_ptr _virtualPropertyManager; diff --git a/include/openspace/interaction/controller.h b/include/openspace/interaction/controller.h index 4e262a0287..baa63c12a3 100644 --- a/include/openspace/interaction/controller.h +++ b/include/openspace/interaction/controller.h @@ -33,7 +33,7 @@ namespace openspace { namespace interaction { -class InteractionHandler; +class NavigationHandler; class Controller { public: @@ -41,10 +41,10 @@ public: _handler(nullptr) {} - void setHandler(InteractionHandler* handler); + void setHandler(NavigationHandler* handler); protected: - InteractionHandler* _handler; + NavigationHandler* _handler; }; } // namespace interaction diff --git a/include/openspace/interaction/delayedvariable.h b/include/openspace/interaction/delayedvariable.h new file mode 100644 index 0000000000..ad8b7e039e --- /dev/null +++ b/include/openspace/interaction/delayedvariable.h @@ -0,0 +1,59 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2017 * + * * + * 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___DELAYEDVARIABLE___H__ +#define __OPENSPACE_CORE___DELAYEDVARIABLE___H__ + +namespace openspace { +namespace interaction { + +/** + * Class that acts as a smoothing filter to a variable. The filter has a step + * response on a form that resembles the function y = 1-e^(-t/scale). The variable + * will be updated as soon as it is set to a value (calling the set() function). +*/ +template +class DelayedVariable { +public: + DelayedVariable(ScaleType scaleFactor, ScaleType friction); + void set(T value, double dt); + void decelerate(double dt); + void setHard(T value); + void setFriction(ScaleType friction); + void setScaleFactor(ScaleType scaleFactor); + T get() const; + +private: + ScaleType _scaleFactor; + ScaleType _friction; + T _targetValue; + T _currentValue; +}; + +} // namespace interaction +} // namespace openspace + +#include "delayedvariable.inl" + +#endif // __OPENSPACE_CORE___DELAYEDVARIABLE___H__ diff --git a/include/openspace/interaction/delayedvariable.inl b/include/openspace/interaction/delayedvariable.inl new file mode 100644 index 0000000000..03e8137f13 --- /dev/null +++ b/include/openspace/interaction/delayedvariable.inl @@ -0,0 +1,76 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2017 * + * * + * 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 + +namespace openspace { +namespace interaction { + +template +DelayedVariable::DelayedVariable(ScaleType scaleFactor, ScaleType friction) + : _scaleFactor(std::move(scaleFactor)) + , _friction(friction) +{ + ghoul_assert(_friction >= ScaleType(0.0), "Friction must be positive"); +} + +template +void DelayedVariable::set(T value, double dt) { + _targetValue = value; + _currentValue = _currentValue + (_targetValue - _currentValue) * + glm::min(_scaleFactor * dt, 1.0); // less or equal to 1.0 keeps it stable +} + +template +void DelayedVariable::decelerate(double dt) { + _currentValue = _currentValue + (- _currentValue) * + glm::min(_scaleFactor * _friction * dt, 1.0); + // less or equal to 1.0 keeps it stable +} + +template +void DelayedVariable::setHard(T value) { + _targetValue = value; + _currentValue = value; +} + +template +void DelayedVariable::setFriction(ScaleType friction) { + _friction = friction; + ghoul_assert(_friction >= ScaleType(0.0), "Friction must be positive"); +} + +template +void DelayedVariable::setScaleFactor(ScaleType scaleFactor) { + _scaleFactor = scaleFactor; +} + +template +T DelayedVariable::get() const { + return _currentValue; +} + +} // namespace interaction +} // namespace openspace diff --git a/include/openspace/interaction/inputstate.h b/include/openspace/interaction/inputstate.h new file mode 100644 index 0000000000..250a51743a --- /dev/null +++ b/include/openspace/interaction/inputstate.h @@ -0,0 +1,70 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2017 * + * * + * 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___INPUTSTATE___H__ +#define __OPENSPACE_CORE___INPUTSTATE___H__ + +#include +#include + +#include + +#include + +namespace openspace { +namespace interaction { + +class InputState { +public: + InputState() = default; + ~InputState() = default; + + // Callback functions + void keyboardCallback(Key key, KeyModifier modifier, KeyAction action); + void mouseButtonCallback(MouseButton button, MouseAction action); + void mousePositionCallback(double mouseX, double mouseY); + void mouseScrollWheelCallback(double mouseScrollDelta); + + // Accessors + const std::list>& getPressedKeys() const; + const std::list& getPressedMouseButtons() const; + glm::dvec2 getMousePosition() const; + double getMouseScrollDelta() const; + + bool isKeyPressed(std::pair keyModPair) const; + bool isKeyPressed(Key key) const; + bool isMouseButtonPressed(MouseButton mouseButton) const; + +private: + // Input from keyboard and mouse + std::list> _keysDown; + std::list _mouseButtonsDown; + glm::dvec2 _mousePosition; + double _mouseScrollDelta; +}; + +} // namespace interaction +} // namespace openspace + +#endif // __OPENSPACE_CORE___INPUTSTATE___H__ diff --git a/include/openspace/interaction/interactionmode.h b/include/openspace/interaction/interactionmode.h deleted file mode 100644 index dc99ec1498..0000000000 --- a/include/openspace/interaction/interactionmode.h +++ /dev/null @@ -1,286 +0,0 @@ -/***************************************************************************************** - * * - * OpenSpace * - * * - * Copyright (c) 2014-2017 * - * * - * 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___INTERACTIONMODE___H__ -#define __OPENSPACE_CORE___INTERACTIONMODE___H__ - -#include -#include -#include -#include - -#ifdef OPENSPACE_MODULE_GLOBEBROWSING_ENABLED -#include -#include -#include -#endif - -#include - -namespace openspace { - -class Camera; -class SceneGraphNode; - -namespace globebrowsing { - class RenderableGlobe; -} - -namespace interaction { - - template - class Interpolator - { - public: - Interpolator(std::function transferFunction) - : _t(0.0) - , _transferFunction(transferFunction) {}; - ~Interpolator() {}; - - void start() { _t = 0.0; }; - void end() { _t = 1.0; }; - void step(double delta) { _t += delta; }; - - T value() { return _transferFunction(_t); }; - bool isInterpolating() { return _t < 1.0; }; - private: - std::function _transferFunction; - double _t; - }; - - class InputState - { - public: - InputState(); - ~InputState(); - - // Callback functions - void keyboardCallback(Key key, KeyModifier modifier, KeyAction action); - void mouseButtonCallback(MouseButton button, MouseAction action); - void mousePositionCallback(double mouseX, double mouseY); - void mouseScrollWheelCallback(double mouseScrollDelta); - - // Accessors - const std::list >& getPressedKeys() const; - const std::list& getPressedMouseButtons() const; - glm::dvec2 getMousePosition() const; - double getMouseScrollDelta() const; - const std::vector& keyframes() const; - - bool isKeyPressed(std::pair keyModPair) const; - bool isKeyPressed(Key key) const; - bool isMouseButtonPressed(MouseButton mouseButton) const; - private: - // Input from keyboard and mouse - std::list > _keysDown; - std::list _mouseButtonsDown; - glm::dvec2 _mousePosition; - double _mouseScrollDelta; - }; - - - -class InteractionMode { -public: - InteractionMode(); - virtual ~InteractionMode(); - - // Mutators - virtual void setFocusNode(SceneGraphNode* focusNode); - - // Accessors - SceneGraphNode* focusNode(); - Interpolator& rotateToFocusNodeInterpolator(); - virtual bool followingNodeRotation() const = 0; - - virtual void updateMouseStatesFromInput(const InputState& inputState, double deltaTime) = 0; - virtual void updateCameraStateFromMouseStates(Camera& camera, double deltaTime) = 0; - -protected: - /** - Inner class that acts as a smoothing filter to a variable. The filter has a step - response on a form that resembles the function y = 1-e^(-t/scale). The variable - will be updates as soon as it is set to a value (calling the set() function). - */ - template - class DelayedVariable { - public: - DelayedVariable(ScaleType scaleFactor, ScaleType friction) { - _scaleFactor = scaleFactor; - _friction = glm::max(friction, ScaleType(0.0)); - } - void set(T value, double dt) { - _targetValue = value; - _currentValue = _currentValue + (_targetValue - _currentValue) * - std::min(_scaleFactor * dt, 1.0); // less or equal to 1.0 keeps it stable - } - void decelerate(double dt) { - _currentValue = _currentValue + (- _currentValue) * - std::min(_scaleFactor * _friction * dt, 1.0); // less or equal to 1.0 keeps it stable - } - void setHard(T value) { - _targetValue = value; - _currentValue = value; - } - void setFriction(ScaleType friction) { - _friction = glm::max(friction, ScaleType(0.0)); - } - void setScaleFactor(ScaleType scaleFactor) { - _scaleFactor = scaleFactor; - } - T get() { - return _currentValue; - } - private: - ScaleType _scaleFactor; - ScaleType _friction; - T _targetValue; - T _currentValue; - }; - - struct MouseState { - MouseState(double scaleFactor) - : velocity(scaleFactor, 1) - , previousPosition(0.0, 0.0) {} - void setFriction(double friction) { - velocity.setFriction(friction); - } - void setVelocityScaleFactor(double scaleFactor) { - velocity.setScaleFactor(scaleFactor); - } - glm::dvec2 previousPosition; - DelayedVariable velocity; - }; - - SceneGraphNode* _focusNode = nullptr; - glm::dvec3 _previousFocusNodePosition; - glm::dquat _previousFocusNodeRotation; - - - Interpolator _rotateToFocusNodeInterpolator; -}; - -class KeyframeInteractionMode : public InteractionMode -{ -public: - struct CameraPose { - glm::dvec3 position; - glm::quat rotation; - std::string focusNode; - bool followFocusNodeRotation; - }; - - KeyframeInteractionMode(); - ~KeyframeInteractionMode(); - - virtual void updateMouseStatesFromInput(const InputState& inputState, double deltaTime); - virtual void updateCameraStateFromMouseStates(Camera& camera, double deltaTime); - bool followingNodeRotation() const override; - Timeline& timeline(); - -private: - Timeline _cameraPoseTimeline; -}; - -class GlobeBrowsingInteractionMode; - -class OrbitalInteractionMode : public InteractionMode -{ -public: - class MouseStates - { - public: - /** - \param sensitivity - \param velocityScaleFactor can be set to 60 to remove the inertia of the - interaction. Lower value will make it harder to move the camera. - */ - MouseStates(double sensitivity, double velocityScaleFactor); - void updateMouseStatesFromInput(const InputState& inputState, double deltaTime); - void setRotationalFriction(double friction); - void setHorizontalFriction(double friction); - void setVerticalFriction(double friction); - void setSensitivity(double sensitivity); - void setVelocityScaleFactor(double scaleFactor); - - glm::dvec2 synchedGlobalRotationMouseVelocity(); - glm::dvec2 synchedLocalRotationMouseVelocity(); - glm::dvec2 synchedTruckMovementMouseVelocity(); - glm::dvec2 synchedLocalRollMouseVelocity(); - glm::dvec2 synchedGlobalRollMouseVelocity(); - - private: - double _sensitivity; - - MouseState _globalRotationMouseState; - MouseState _localRotationMouseState; - MouseState _truckMovementMouseState; - MouseState _localRollMouseState; - MouseState _globalRollMouseState; - }; - - OrbitalInteractionMode(std::shared_ptr mouseStates); - ~OrbitalInteractionMode(); - - //virtual void update(Camera& camera, const InputState& inputState, double deltaTime); - - virtual void updateMouseStatesFromInput(const InputState& inputState, double deltaTime); - virtual void updateCameraStateFromMouseStates(Camera& camera, double deltaTime); - bool followingNodeRotation() const override; - -protected: - //void updateCameraStateFromMouseStates(Camera& camera, double deltaTime); - std::shared_ptr _mouseStates; -}; - -class GlobeBrowsingInteractionMode : public OrbitalInteractionMode -{ -public: - GlobeBrowsingInteractionMode(std::shared_ptr mouseStates); - ~GlobeBrowsingInteractionMode(); - - virtual void setFocusNode(SceneGraphNode* focusNode); - //virtual void update(Camera& camera, const InputState& inputState, double deltaTime); - virtual void updateCameraStateFromMouseStates(Camera& camera, double deltaTime); - bool followingNodeRotation() const override; -#ifdef OPENSPACE_MODULE_GLOBEBROWSING_ENABLED - void goToChunk(Camera& camera, globebrowsing::TileIndex ti, glm::vec2 uv, - bool resetCameraDirection); - void goToGeodetic2(Camera& camera, globebrowsing::Geodetic2 geo2, - bool resetCameraDirection); - - void goToGeodetic3(Camera& camera, globebrowsing::Geodetic3 geo3); - void resetCameraDirection(Camera& camera, globebrowsing::Geodetic2 geo2); -#endif -private: - //void updateCameraStateFromMouseStates(Camera& camera, double deltaTime); -#ifdef OPENSPACE_MODULE_GLOBEBROWSING_ENABLED - globebrowsing::RenderableGlobe* _globe; -#endif -}; - -} // namespace interaction -} // namespace openspace - -#endif // __OPENSPACE_CORE___INTERACTIONMODE___H__ diff --git a/include/openspace/interaction/interpolator.h b/include/openspace/interaction/interpolator.h new file mode 100644 index 0000000000..cf4bafb25d --- /dev/null +++ b/include/openspace/interaction/interpolator.h @@ -0,0 +1,65 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2017 * + * * + * 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___INTERPOLATOR___H__ +#define __OPENSPACE_CORE___INTERPOLATOR___H__ + +#include + +namespace openspace { +namespace interaction { + +/* + * Interpolates a typename T using a transfer function. + */ +template +class Interpolator { +public: + Interpolator(); + ~Interpolator() = default; + + void start(); + void end(); + void setDeltaTime(float deltaTime); + void setTransferFunction(std::function transferFunction); + void setInterpolationTime(float interpolationTime); + void step(); + + float deltaTimeScaled() const; + T value() const; + bool isInterpolating() const; + +private: + std::function _transferFunction; + float _t; + float _interpolationTime; + float _scaledDeltaTime; +}; + +} // namespace interaction +} // namespace openspace + +#include "interpolator.inl" + +#endif // __OPENSPACE_CORE___INTERPOLATOR___H__ diff --git a/include/openspace/interaction/interpolator.inl b/include/openspace/interaction/interpolator.inl new file mode 100644 index 0000000000..c9ad0ed612 --- /dev/null +++ b/include/openspace/interaction/interpolator.inl @@ -0,0 +1,85 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2017 * + * * + * 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 + +namespace openspace { +namespace interaction { + +template +Interpolator::Interpolator() +: _transferFunction([](float t){ return t; }) +, _t(0.0) +, _interpolationTime(1.0) {}; + +template +void Interpolator::start() { + _t = 0.0; +}; + +template +void Interpolator::end() { + _t = 1.0; +} + +template +void Interpolator::setDeltaTime(float deltaTime) { + _scaledDeltaTime = deltaTime / _interpolationTime; +} + +template +void Interpolator::setTransferFunction(std::function transferFunction) { + _transferFunction = transferFunction; +} + +template +void Interpolator::setInterpolationTime(float interpolationTime) { + _interpolationTime = interpolationTime; +} + +template +void Interpolator::step() { + _t += _scaledDeltaTime; + _t = glm::clamp(_t, 0.0f, 1.0f); +} + +template +float Interpolator::deltaTimeScaled() const { + return _scaledDeltaTime; +} + +template +T Interpolator::value() const { + return _transferFunction(_t); +} + +template +bool Interpolator::isInterpolating() const { + return _t < 1.0 && _t >= 0.0; +} + +} // namespace interaction +} // namespace openspace diff --git a/include/openspace/interaction/keybindingmanager.h b/include/openspace/interaction/keybindingmanager.h new file mode 100644 index 0000000000..2d4432f06f --- /dev/null +++ b/include/openspace/interaction/keybindingmanager.h @@ -0,0 +1,87 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2017 * + * * + * 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___KEYBINDINGMANAGER___H__ +#define __OPENSPACE_CORE___KEYBINDINGMANAGER___H__ + +#include +#include +#include + +#include + +namespace openspace { + +class Camera; +class SceneGraphNode; + +namespace interaction { + +class KeyBindingManager : public DocumentationGenerator +{ +public: + KeyBindingManager(); + ~KeyBindingManager() = default; + + void resetKeyBindings(); + + void bindKeyLocal( + Key key, + KeyModifier modifier, + std::string luaCommand, + std::string documentation = "" + ); + + void bindKey( + Key key, + KeyModifier modifier, + std::string luaCommand, + std::string documentation = "" + ); + + static scripting::LuaLibrary luaLibrary(); + + // Callback functions + void keyboardCallback(Key key, KeyModifier modifier, KeyAction action); + +private: + using Synchronized = ghoul::Boolean; + + struct KeyInformation { + std::string command; + Synchronized synchronization; + std::string documentation; + }; + + std::string generateJson() const override; + + bool _cameraUpdatedFromScript = false; + + std::multimap _keyLua; +}; + +} // namespace interaction +} // namespace openspace + +#endif // __OPENSPACE_CORE___KEYBINDINGMANAGER___H__ diff --git a/include/openspace/interaction/keyframenavigator.h b/include/openspace/interaction/keyframenavigator.h new file mode 100644 index 0000000000..511e2ae3df --- /dev/null +++ b/include/openspace/interaction/keyframenavigator.h @@ -0,0 +1,69 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2017 * + * * + * 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___KEYFRAMENAVIGATOR___H__ +#define __OPENSPACE_CORE___KEYFRAMENAVIGATOR___H__ + +#include +#include + +#include +#include + +namespace openspace { + +class Camera; + +namespace interaction { + +class KeyframeNavigator +{ +public: + struct CameraPose { + glm::dvec3 position; + glm::quat rotation; + std::string focusNode; + bool followFocusNodeRotation; + }; + + KeyframeNavigator() = default; + ~KeyframeNavigator() = default; + + void updateCamera(Camera& camera); + Timeline& timeline(); + + void addKeyframe(double timestamp, KeyframeNavigator::CameraPose pose); + void removeKeyframesAfter(double timestamp); + void clearKeyframes(); + size_t nKeyframes() const; + const std::vector& keyframes() const; + +private: + Timeline _cameraPoseTimeline; +}; + +} // namespace interaction +} // namespace openspace + +#endif // __OPENSPACE_CORE___KEYFRAMENAVIGATOR___H__ diff --git a/include/openspace/interaction/mousestate.h b/include/openspace/interaction/mousestate.h new file mode 100644 index 0000000000..c6db1380a7 --- /dev/null +++ b/include/openspace/interaction/mousestate.h @@ -0,0 +1,80 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2017 * + * * + * 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___MOUSESTATE___H__ +#define __OPENSPACE_CORE___MOUSESTATE___H__ + +#include +#include + +#include + +namespace openspace { +namespace interaction { + +struct MouseState { + MouseState(double scaleFactor); + void setFriction(double friction); + void setVelocityScaleFactor(double scaleFactor); + + glm::dvec2 previousPosition; + DelayedVariable velocity; +}; + +class MouseStates +{ +public: + /** + \param sensitivity + \param velocityScaleFactor can be set to 60 to remove the inertia of the + interaction. Lower value will make it harder to move the camera. + */ + MouseStates(double sensitivity, double velocityScaleFactor); + void updateMouseStatesFromInput(const InputState& inputState, double deltaTime); + void setRotationalFriction(double friction); + void setHorizontalFriction(double friction); + void setVerticalFriction(double friction); + void setSensitivity(double sensitivity); + void setVelocityScaleFactor(double scaleFactor); + + glm::dvec2 globalRotationMouseVelocity() const; + glm::dvec2 localRotationMouseVelocity() const; + glm::dvec2 truckMovementMouseVelocity() const; + glm::dvec2 localRollMouseVelocity() const; + glm::dvec2 globalRollMouseVelocity() const; + +private: + double _sensitivity; + + MouseState _globalRotationMouseState; + MouseState _localRotationMouseState; + MouseState _truckMovementMouseState; + MouseState _localRollMouseState; + MouseState _globalRollMouseState; +}; + +} // namespace interaction +} // namespace openspace + +#endif // __OPENSPACE_CORE___MOUSESTATE___H__ diff --git a/include/openspace/interaction/interactionhandler.h b/include/openspace/interaction/navigationhandler.h similarity index 59% rename from include/openspace/interaction/interactionhandler.h rename to include/openspace/interaction/navigationhandler.h index 162404d227..2d0c930d4a 100644 --- a/include/openspace/interaction/interactionhandler.h +++ b/include/openspace/interaction/navigationhandler.h @@ -22,14 +22,13 @@ * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * ****************************************************************************************/ -#ifndef __OPENSPACE_CORE___INTERACTIONHANDLER___H__ -#define __OPENSPACE_CORE___INTERACTIONHANDLER___H__ +#ifndef __OPENSPACE_CORE___NAVIGATIONHANDLER___H__ +#define __OPENSPACE_CORE___NAVIGATIONHANDLER___H__ -#include #include -#include -#include +#include +#include #include #include #include @@ -38,10 +37,6 @@ #include -#include - -#include - namespace openspace { class Camera; @@ -49,11 +44,10 @@ class SceneGraphNode; namespace interaction { -class InteractionHandler : public properties::PropertyOwner, public DocumentationGenerator -{ +class NavigationHandler : public properties::PropertyOwner { public: - InteractionHandler(); - ~InteractionHandler(); + NavigationHandler(); + ~NavigationHandler(); void initialize(); void deinitialize(); @@ -63,39 +57,9 @@ public: void setCamera(Camera* camera); void resetCameraDirection(); - // Interaction mode setters - void setCameraStateFromDictionary(const ghoul::Dictionary& cameraDict); - InteractionMode* interactionMode(); + void setCameraStateFromDictionary(const ghoul::Dictionary& cameraDict); - void goToChunk(int x, int y, int level); - void goToGeo(double latitude, double longitude); - - void resetKeyBindings(); - - void addKeyframe(double timestamp, KeyframeInteractionMode::CameraPose pose); - void removeKeyframesAfter(double timestamp); - void clearKeyframes(); - size_t nKeyframes() const; - const std::vector& keyframes() const; - - void bindKeyLocal( - Key key, - KeyModifier modifier, - std::string luaCommand, - std::string documentation = "" - ); - void bindKey( - Key key, - KeyModifier modifier, - std::string luaCommand, - std::string documentation = "" - ); - void lockControls(); - void unlockControls(); - - //void update(double deltaTime); void updateCamera(double deltaTime); - void updateInputStates(double timeSinceLastUpdate); // Accessors ghoul::Dictionary getCameraStateDictionary(); @@ -104,15 +68,8 @@ public: glm::quat focusNodeToCameraRotation() const; Camera* camera() const; const InputState& inputState() const; - - /** - * Returns the Lua library that contains all Lua functions available to affect the - * interaction. The functions contained are - * - openspace::luascriptfunctions::setOrigin - * \return The Lua library that contains all Lua functions available to affect the - * interaction - */ - static scripting::LuaLibrary luaLibrary(); + const OrbitalNavigator& orbitalNavigator() const; + KeyframeNavigator& keyframeNavigator() const; // Callback functions void keyboardCallback(Key key, KeyModifier modifier, KeyAction action); @@ -123,47 +80,26 @@ public: void saveCameraStateToFile(const std::string& filepath); void restoreCameraStateFromFile(const std::string& filepath); + /** + * \return The Lua library that contains all Lua functions available to affect the + * interaction + */ + static scripting::LuaLibrary luaLibrary(); private: - using Synchronized = ghoul::Boolean; - - struct KeyInformation { - std::string command; - Synchronized synchronization; - std::string documentation; - }; - - std::string generateJson() const override; - - void setInteractionMode(InteractionMode* interactionMode); - bool _cameraUpdatedFromScript = false; - std::multimap _keyLua; - std::unique_ptr _inputState; Camera* _camera; - InteractionMode* _currentInteractionMode; - - std::shared_ptr _mouseStates; - - std::unique_ptr _orbitalInteractionMode; - std::unique_ptr _globeBrowsingInteractionMode; - std::unique_ptr _keyframeInteractionMode; + std::unique_ptr _orbitalNavigator; + std::unique_ptr _keyframeNavigator; // Properties properties::StringProperty _origin; - properties::OptionProperty _interactionModeOption; - - properties::BoolProperty _rotationalFriction; - properties::BoolProperty _horizontalFriction; - properties::BoolProperty _verticalFriction; - - properties::FloatProperty _sensitivity; - properties::FloatProperty _rapidness; + properties::BoolProperty _useKeyFrameInteraction; }; } // namespace interaction } // namespace openspace -#endif // __OPENSPACE_CORE___INTERACTIONHANDLER___H__ +#endif // __OPENSPACE_CORE___NAVIGATIONHANDLER___H__ diff --git a/include/openspace/interaction/orbitalnavigator.h b/include/openspace/interaction/orbitalnavigator.h new file mode 100644 index 0000000000..bdd97f39ba --- /dev/null +++ b/include/openspace/interaction/orbitalnavigator.h @@ -0,0 +1,197 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2017 * + * * + * 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___ORBITALNAVIGATOR___H__ +#define __OPENSPACE_CORE___ORBITALNAVIGATOR___H__ + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +namespace openspace { + +class SceneGraphNode; +class Camera; +class SurfacePositionHandle; + +namespace interaction { + +class OrbitalNavigator : public properties::PropertyOwner { +public: + OrbitalNavigator(); + ~OrbitalNavigator(); + + void updateMouseStatesFromInput(const InputState& inputState, double deltaTime); + void updateCameraStateFromMouseStates(Camera& camera, double deltaTime); + void setFocusNode(SceneGraphNode* focusNode); + void startInterpolateCameraDirection(const Camera& camera); + + bool followingNodeRotation() const; + SceneGraphNode* focusNode() const; + +private: + struct CameraRotationDecomposition { + glm::dquat localRotation; + glm::dquat globalRotation; + }; + + // Properties + properties::BoolProperty _rotationalFriction; + properties::BoolProperty _horizontalFriction; + properties::BoolProperty _verticalFriction; + properties::FloatProperty _followFocusNodeRotationDistance; + properties::FloatProperty _minimumAllowedDistance; + properties::FloatProperty _sensitivity; + properties::FloatProperty _motionLag; + + MouseStates _mouseStates; + + SceneGraphNode* _focusNode = nullptr; + glm::dvec3 _previousFocusNodePosition; + glm::dquat _previousFocusNodeRotation; + + Interpolator _rotateToFocusNodeInterpolator; + Interpolator _followRotationInterpolator; + + /** + * Decomposes the cameras rotation in to a global and a local rotation defined by + * CameraRotationDecomposition. The global rotation defines the rotation so that the + * camera points towards the focus node in the direction opposite to the direction + * out from the surface of the object. The local rotation defines the differential + * from the global to the current total rotation so that + * cameraRotation = globalRotation * localRotation. + */ + CameraRotationDecomposition decomposeCameraRotation( + const glm::dvec3& cameraPosition, + const glm::dquat& cameraRotation, + const glm::dvec3& cameraLookUp, + const glm::dvec3& cameraViewDirection); + + /* + * Perform a camera roll on the local camera rotation + * \returns a local camera rotation modified with a roll. + */ + glm::dquat roll(double deltaTime, const glm::dquat& localCameraRotation) const; + + /** + * Performs rotation around the cameras x and y axes. + * \returns a local camera rotation modified with two degrees of freedom. + */ + glm::dquat rotateLocally(double deltaTime, + const glm::dquat& localCameraRotation) const; + + /** + * Interpolates the local rotation towards a 0 rotation. + * \returns a modified local rotation interpolated towards 0. + */ + glm::dquat interpolateLocalRotation(double deltaTime, + const glm::dquat& localCameraRotation); + + /** + * Translates the horizontal direction. If far from the focus object, this will + * result in an orbital rotation around the object. This function does not affect the + * rotation but only the position. + * \returns a position vector adjusted in the horizontal direction. + */ + glm::dvec3 translateHorizontally(double deltaTime, const glm::dvec3& cameraPosition, + const glm::dvec3& objectPosition, + const glm::dquat& focusNodeRotationDiff, + const glm::dquat& globalCameraRotation, + const SurfacePositionHandle& positionHandle) const; + + /* + * Adds rotation to the camera position so that it follows the rotation of the focus + * node defined by the differential focusNodeRotationDiff. + * \returns a position updated with the rotation defined by focusNodeRotationDiff + */ + glm::dvec3 followFocusNodeRotation(const glm::dvec3& cameraPosition, + const glm::dvec3& objectPosition, + const glm::dquat& focusNodeRotationDiff) const; + + /** + * Updates the global rotation so that it points towards the focus node. + * \returns a global rotation quaternion defining a rotation towards the focus node. + */ + glm::dquat rotateGlobally(const glm::dquat& globalCameraRotation, + const glm::dvec3& objectPosition, + const glm::dquat& focusNodeRotationDiff, + const glm::dvec3& cameraPosition, + const SurfacePositionHandle& positionHandle) const; + + /** + * Translates the camera position towards or away from the focus node. + * \returns a position vector adjusted in the vertical direction. + */ + glm::dvec3 translateVertically(double deltaTime, const glm::dvec3& cameraPosition, + const glm::dvec3& objectPosition, + const SurfacePositionHandle& positionHandle) const; + + /** + * Rotates the camera around the out vector of the surface. + * \returns a quaternion adjusted to rotate around the out vector of the surface. + */ + glm::dquat rotateHorizontally(double deltaTime, + const glm::dquat& globalCameraRotation, + const glm::dvec3& cameraPosition, + const SurfacePositionHandle& positionHandle) const; + + /** + * Push the camera out to the surface of the object. + * \returns a position vector adjusted to be at least minHeightAboveGround meters + * above the actual surface of the object + */ + glm::dvec3 pushToSurface(double minHeightAboveGround, + const glm::dvec3& cameraPosition, + const glm::dvec3& objectPosition, + const SurfacePositionHandle& positionHandle) const; + + /** + * Interpolates between rotationDiff and a 0 rotation. + */ + glm::dquat interpolateRotationDifferential( + double deltaTime, double interpolationTime, + const glm::dquat& rotationDiff, + const glm::dvec3& objectPosition, + const glm::dvec3& cameraPosition, + const SurfacePositionHandle& positionHandle); + + /** + * Calculates a SurfacePositionHandle given a camera position in world space. + */ + SurfacePositionHandle calculateSurfacePositionHandle( + const glm::dvec3 cameraPositionWorldSpace); +}; + +} // namespace interaction +} // namespace openspace + +#endif // __OPENSPACE_CORE___ORBITALNAVIGATOR___H__ diff --git a/include/openspace/rendering/renderable.h b/include/openspace/rendering/renderable.h index b9fe5cf7a3..a5bafcc8d5 100644 --- a/include/openspace/rendering/renderable.h +++ b/include/openspace/rendering/renderable.h @@ -42,6 +42,7 @@ namespace openspace { struct RenderData; struct UpdateData; struct RendererTasks; +struct SurfacePositionHandle; namespace documentation { struct Documentation; } @@ -78,6 +79,8 @@ public: virtual void render(const RenderData& data); virtual void render(const RenderData& data, RendererTasks& rendererTask); virtual void update(const UpdateData& data); + virtual SurfacePositionHandle calculateSurfacePositionHandle( + const glm::dvec3& targetModelSpace); RenderBin renderBin() const; void setRenderBin(RenderBin bin); diff --git a/include/openspace/scene/scenegraphnode.h b/include/openspace/scene/scenegraphnode.h index de8d49b787..7f940aafc7 100644 --- a/include/openspace/scene/scenegraphnode.h +++ b/include/openspace/scene/scenegraphnode.h @@ -48,6 +48,7 @@ class Translation; class Scale; class Scene; struct UpdateData; +struct SurfacePositionHandle; namespace documentation { struct Documentation; } @@ -91,6 +92,8 @@ public: void removeDependency(SceneGraphNode& dependency, UpdateScene updateScene = UpdateScene::Yes); void clearDependencies(UpdateScene updateScene = UpdateScene::Yes); void setDependencies(const std::vector& dependencies, UpdateScene updateScene = UpdateScene::Yes); + SurfacePositionHandle calculateSurfacePositionHandle( + const glm::dvec3& targetModelSpace); const std::vector& dependencies() const; const std::vector& dependentNodes() const; diff --git a/include/openspace/util/updatestructures.h b/include/openspace/util/updatestructures.h index 6e0c007e72..47609c7994 100644 --- a/include/openspace/util/updatestructures.h +++ b/include/openspace/util/updatestructures.h @@ -75,6 +75,21 @@ struct RaycastData { std::string namespaceName; }; +/** + * Defines the position of an object relative to a surface. The surface is defined as + * a reference surface together with a height offset from that reference surface. + */ +struct SurfacePositionHandle { + /// Vector from the center of the object to the reference surface of the object + glm::dvec3 centerToReferenceSurface; + /// Direction out from the reference. Can conincide with the surface normal but does + /// not have to. + glm::dvec3 referenceSurfaceOutDirection; + /// Height from the reference surface out to the actual surface in the direction of + /// the surface normal. Can be positive or negative. + double heightToSurface; +}; + } // namespace openspace #endif // __OPENSPACE_CORE___UPDATESTRUCTURES___H__ diff --git a/modules/globebrowsing/chunk/chunk.cpp b/modules/globebrowsing/chunk/chunk.cpp index aafd7a0b2a..dca2d4697d 100644 --- a/modules/globebrowsing/chunk/chunk.cpp +++ b/modules/globebrowsing/chunk/chunk.cpp @@ -102,7 +102,7 @@ Chunk::BoundingHeights Chunk::getBoundingHeights() const { // a single raster image. If it is not we will just use the first raster // (that is channel 0). const size_t HeightChannel = 0; - const LayerGroup& heightmaps = layerManager->layerGroup(layergroupid::HeightLayers); + const LayerGroup& heightmaps = layerManager->layerGroup(layergroupid::GroupID::HeightLayers); std::vector chunkTileSettingPairs = tileselector::getTilesAndSettingsUnsorted( heightmaps, _tileIndex); diff --git a/modules/globebrowsing/geometry/geodeticpatch.h b/modules/globebrowsing/geometry/geodeticpatch.h index e47cd5de3d..eff1392431 100644 --- a/modules/globebrowsing/geometry/geodeticpatch.h +++ b/modules/globebrowsing/geometry/geodeticpatch.h @@ -27,11 +27,10 @@ #include #include +#include namespace openspace { namespace globebrowsing { - -struct TileIndex; class GeodeticPatch { public: diff --git a/modules/globebrowsing/globebrowsingmodule.cpp b/modules/globebrowsing/globebrowsingmodule.cpp index 546029c3b1..0f5a8c2c24 100644 --- a/modules/globebrowsing/globebrowsingmodule.cpp +++ b/modules/globebrowsing/globebrowsingmodule.cpp @@ -25,8 +25,11 @@ #include #include +#include +#include #include #include +#include #include #include #include @@ -37,11 +40,11 @@ #include #include #include - #include #include #include +#include #include #include @@ -51,6 +54,10 @@ #include "globebrowsingmodule_lua.inl" +namespace { + const char* _loggerCat = "GlobeBrowsingModule"; +} + namespace openspace { GlobeBrowsingModule::GlobeBrowsingModule() : OpenSpaceModule(Name) {} @@ -146,6 +153,18 @@ scripting::LuaLibrary GlobeBrowsingModule::luaLibrary() const { "any of " + listLayerGroups + ". The third argument is the dictionary" "defining the layer." }, + { + "goToChunk", + &globebrowsing::luascriptfunctions::goToChunk, + "void", + "Go to chunk with given index x, y, level" + }, + { + "goToGeo", + &globebrowsing::luascriptfunctions::goToGeo, + "void", + "Go to geographic coordinates latitude and longitude" + } }, { "${MODULE_GLOBEBROWSING}/scripts/layer_support.lua" @@ -156,6 +175,163 @@ scripting::LuaLibrary GlobeBrowsingModule::luaLibrary() const { }; } +void GlobeBrowsingModule::goToChunk(int x, int y, int level) { + using namespace globebrowsing; + Camera* cam = OsEng.navigationHandler().camera(); + goToChunk(*cam, TileIndex(x,y,level), glm::vec2(0.5, 0.5), true); +} + +void GlobeBrowsingModule::goToGeo(double latitude, double longitude) { + using namespace globebrowsing; + Camera* cam = OsEng.navigationHandler().camera(); + goToGeodetic2(*cam, Geodetic2(latitude, longitude) / 180 * glm::pi(), true); +} + +void GlobeBrowsingModule::goToGeo(double latitude, double longitude, + double altitude) +{ + using namespace globebrowsing; + + Camera* cam = OsEng.navigationHandler().camera(); + goToGeodetic3( + *cam, + { + Geodetic2(latitude, longitude) / 180 * glm::pi(), + altitude + }, + true + ); +} + +void GlobeBrowsingModule::goToChunk(Camera& camera, globebrowsing::TileIndex ti, + glm::vec2 uv, bool resetCameraDirection) +{ + using namespace globebrowsing; + + RenderableGlobe* globe = castFocusNodeRenderableToGlobe(); + if (!globe) { + LERROR("Focus node must have a RenderableGlobe renderable."); + return; + } + + // Camera position in model space + glm::dvec3 camPos = camera.positionVec3(); + glm::dmat4 inverseModelTransform = globe->inverseModelTransform(); + glm::dvec3 cameraPositionModelSpace = + glm::dvec3(inverseModelTransform * glm::dvec4(camPos, 1)); + + GeodeticPatch patch(ti); + Geodetic2 corner = patch.getCorner(SOUTH_WEST); + Geodetic2 positionOnPatch = patch.getSize(); + positionOnPatch.lat *= uv.y; + positionOnPatch.lon *= uv.x; + Geodetic2 pointPosition = corner + positionOnPatch; + + glm::dvec3 positionOnEllipsoid = + globe->ellipsoid().geodeticSurfaceProjection(cameraPositionModelSpace); + double altitude = glm::length(cameraPositionModelSpace - positionOnEllipsoid); + + goToGeodetic3(camera, {pointPosition, altitude}, resetCameraDirection); +} + +void GlobeBrowsingModule::goToGeodetic2(Camera& camera, + globebrowsing::Geodetic2 geo2, + bool resetCameraDirection) +{ + using namespace globebrowsing; + + RenderableGlobe* globe = castFocusNodeRenderableToGlobe(); + if (!globe) { + LERROR("Focus node must have a RenderableGlobe renderable."); + return; + } + + // Camera position in model space + glm::dvec3 camPos = camera.positionVec3(); + glm::dmat4 inverseModelTransform = globe->inverseModelTransform(); + glm::dvec3 cameraPositionModelSpace = + glm::dvec3(inverseModelTransform * glm::dvec4(camPos, 1)); + + glm::dvec3 positionOnEllipsoid = + globe->ellipsoid().geodeticSurfaceProjection(cameraPositionModelSpace); + double altitude = glm::length(cameraPositionModelSpace - positionOnEllipsoid); + + goToGeodetic3(camera, {geo2, altitude}, resetCameraDirection); +} + +void GlobeBrowsingModule::goToGeodetic3(Camera& camera, globebrowsing::Geodetic3 geo3, + bool resetCameraDirection) +{ + using namespace globebrowsing; + + RenderableGlobe* globe = castFocusNodeRenderableToGlobe(); + if (!globe) { + LERROR("Focus node must have a RenderableGlobe renderable."); + return; + } + + glm::dvec3 positionModelSpace = globe->ellipsoid().cartesianPosition(geo3); + glm::dmat4 modelTransform = globe->modelTransform(); + glm::dvec3 positionWorldSpace = modelTransform * glm::dvec4(positionModelSpace, 1.0); + camera.setPositionVec3(positionWorldSpace); + + if (resetCameraDirection) { + this->resetCameraDirection(camera, geo3.geodetic2); + } +} + +void GlobeBrowsingModule::resetCameraDirection(Camera& camera, globebrowsing::Geodetic2 geo2) +{ + using namespace globebrowsing; + + RenderableGlobe* globe = castFocusNodeRenderableToGlobe(); + if (!globe) { + LERROR("Focus node must have a RenderableGlobe renderable."); + return; + } + + // Camera is described in world space + glm::dmat4 modelTransform = globe->modelTransform(); + + // Lookup vector + glm::dvec3 positionModelSpace = globe->ellipsoid().cartesianSurfacePosition(geo2); + glm::dvec3 slightlyNorth = globe->ellipsoid().cartesianSurfacePosition( + Geodetic2(geo2.lat + 0.001, geo2.lon)); + glm::dvec3 lookUpModelSpace = glm::normalize(slightlyNorth - positionModelSpace); + glm::dvec3 lookUpWorldSpace = glm::dmat3(modelTransform) * lookUpModelSpace; + + // Lookat vector + glm::dvec3 lookAtWorldSpace = modelTransform * glm::dvec4(positionModelSpace, 1.0); + + // Eye position + glm::dvec3 eye = camera.positionVec3(); + + // Matrix + glm::dmat4 lookAtMatrix = glm::lookAt( + eye, lookAtWorldSpace, lookUpWorldSpace); + + // Set rotation + glm::dquat rotation = glm::quat_cast(inverse(lookAtMatrix)); + camera.setRotation(rotation); +} + +globebrowsing::RenderableGlobe* GlobeBrowsingModule::castFocusNodeRenderableToGlobe() { + using namespace globebrowsing; + + Renderable* baseRenderable = OsEng.navigationHandler().focusNode()->renderable(); + if (!baseRenderable) { + return nullptr; + } + if (globebrowsing::RenderableGlobe* globe = + dynamic_cast(baseRenderable)) + { + return globe; + } + else { + return nullptr; + } +} + std::string GlobeBrowsingModule::layerGroupNamesList() { std::string listLayerGroups(""); for (int i = 0; i < globebrowsing::layergroupid::NUM_LAYER_GROUPS - 1; ++i) { diff --git a/modules/globebrowsing/globebrowsingmodule.h b/modules/globebrowsing/globebrowsingmodule.h index 718190c9e0..2faad330ef 100644 --- a/modules/globebrowsing/globebrowsingmodule.h +++ b/modules/globebrowsing/globebrowsingmodule.h @@ -26,11 +26,17 @@ #define __OPENSPACE_MODULE_GLOBEBROWSING___GLOBEBROWSING_MODULE___H__ #include +#include #include namespace openspace { - + class Camera; namespace globebrowsing { + class RenderableGlobe; + class TileIndex; + class Geodetic2; + class Geodetic3; + namespace cache { class MemoryAwareTileCache; } @@ -42,14 +48,26 @@ public: GlobeBrowsingModule(); - globebrowsing::cache::MemoryAwareTileCache* tileCache(); - + void goToChunk(int x, int y, int level); + void goToGeo(double latitude, double longitude); + void goToGeo(double latitude, double longitude, double altitude); + globebrowsing::cache::MemoryAwareTileCache* tileCache(); scripting::LuaLibrary luaLibrary() const override; protected: void internalInitialize() override; private: + + void goToChunk(Camera& camera, globebrowsing::TileIndex ti, glm::vec2 uv, + bool resetCameraDirection); + void goToGeodetic2(Camera& camera, globebrowsing::Geodetic2 geo2, + bool resetCameraDirection); + void goToGeodetic3(Camera& camera, globebrowsing::Geodetic3 geo3, + bool resetCameraDirection); + void resetCameraDirection(Camera& camera, globebrowsing::Geodetic2 geo2); + globebrowsing::RenderableGlobe* castFocusNodeRenderableToGlobe(); + /** \return a comma separated list of layer group names. */ diff --git a/modules/globebrowsing/globebrowsingmodule_lua.inl b/modules/globebrowsing/globebrowsingmodule_lua.inl index cd6dbfb9f7..c5fe4bf5db 100644 --- a/modules/globebrowsing/globebrowsingmodule_lua.inl +++ b/modules/globebrowsing/globebrowsingmodule_lua.inl @@ -29,6 +29,7 @@ #include #include +#include #include #include #include @@ -135,6 +136,47 @@ int deleteLayer(lua_State* L) { return 0; } +int goToChunk(lua_State* L) { + using ghoul::lua::luaTypeToString; + + // Check arguments + int nArguments = lua_gettop(L); + if (nArguments != 3) { + return luaL_error(L, "Expected %i arguments, got %i", 3, nArguments); + } + + int x = static_cast(lua_tonumber(L, 1)); + int y = static_cast(lua_tonumber(L, 2)); + int level = static_cast(lua_tonumber(L, 3)); + + OsEng.moduleEngine().module()->goToChunk(x, y, level); + + return 0; +} + +int goToGeo(lua_State* L) { + using ghoul::lua::luaTypeToString; + + int nArguments = lua_gettop(L); + if (nArguments != 2 && nArguments != 3) { + return luaL_error(L, "Expected 2 or 3 arguments."); + } + + double latitude = static_cast(lua_tonumber(L, 1)); + double longitude = static_cast(lua_tonumber(L, 2)); + + if (nArguments == 2) { + OsEng.moduleEngine().module()->goToGeo(latitude, longitude); + } + else if (nArguments == 3) { + double altitude = static_cast(lua_tonumber(L, 3)); + OsEng.moduleEngine().module()->goToGeo(latitude, longitude, + altitude); + } + + return 0; +} + } // namespace luascriptfunctions } // nameapace globebrowsing diff --git a/modules/globebrowsing/globes/chunkedlodglobe.cpp b/modules/globebrowsing/globes/chunkedlodglobe.cpp index 2014f0c20c..60de75ff00 100644 --- a/modules/globebrowsing/globes/chunkedlodglobe.cpp +++ b/modules/globebrowsing/globes/chunkedlodglobe.cpp @@ -174,7 +174,7 @@ float ChunkedLodGlobe::getHeight(glm::dvec3 position) const { // Get the tile providers for the height maps const std::vector>& heightMapLayers = - _layerManager->layerGroup(layergroupid::HeightLayers).activeLayers(); + _layerManager->layerGroup(layergroupid::GroupID::HeightLayers).activeLayers(); for (const std::shared_ptr& layer : heightMapLayers) { tileprovider::TileProvider* tileProvider = layer->tileProvider(); diff --git a/modules/globebrowsing/globes/renderableglobe.cpp b/modules/globebrowsing/globes/renderableglobe.cpp index 38c902841f..6b61e0f7d0 100644 --- a/modules/globebrowsing/globes/renderableglobe.cpp +++ b/modules/globebrowsing/globes/renderableglobe.cpp @@ -32,8 +32,6 @@ namespace { const char* keyFrame = "Frame"; const char* keyRadii = "Radii"; - const char* keyInteractionDepthBelowEllipsoid = "InteractionDepthBelowEllipsoid"; - const char* keyCameraMinHeight = "CameraMinHeight"; const char* keySegmentsPerPatch = "SegmentsPerPatch"; const char* keyLayers = "Layers"; } @@ -86,15 +84,6 @@ RenderableGlobe::RenderableGlobe(const ghoul::Dictionary& dictionary) double patchSegmentsd; dictionary.getValue(keySegmentsPerPatch, patchSegmentsd); int patchSegments = patchSegmentsd; - - if (!dictionary.getValue(keyInteractionDepthBelowEllipsoid, - _interactionDepthBelowEllipsoid)) { - _interactionDepthBelowEllipsoid = 0; - } - - float cameraMinHeight; - dictionary.getValue(keyCameraMinHeight, cameraMinHeight); - _generalProperties.cameraMinHeight.set(cameraMinHeight); // Init layer manager ghoul::Dictionary layersDictionary; @@ -262,8 +251,33 @@ const std::shared_ptr RenderableGlobe::savedCamera() const { return _savedCamera; } -double RenderableGlobe::interactionDepthBelowEllipsoid() { - return _interactionDepthBelowEllipsoid; +SurfacePositionHandle RenderableGlobe::calculateSurfacePositionHandle( + const glm::dvec3& targetModelSpace) +{ + glm::dvec3 centerToEllipsoidSurface = + _ellipsoid.geodeticSurfaceProjection(targetModelSpace); + glm::dvec3 ellipsoidSurfaceToTarget = targetModelSpace - centerToEllipsoidSurface; + // ellipsoidSurfaceOutDirection will point towards the target, we want the outward + // direction. Therefore it must be flipped in case the target is under the reference + // ellipsoid so that it always points outwards + glm::dvec3 ellipsoidSurfaceOutDirection = glm::normalize(ellipsoidSurfaceToTarget); + if (glm::dot(ellipsoidSurfaceOutDirection, centerToEllipsoidSurface) < 0) { + ellipsoidSurfaceOutDirection *= -1.0; + } + + double heightToSurface = getHeight(targetModelSpace); + heightToSurface = glm::isnan(heightToSurface) ? 0.0 : heightToSurface; + centerToEllipsoidSurface = glm::isnan(glm::length(centerToEllipsoidSurface)) ? + (glm::dvec3(0.0, 1.0, 0.0) * static_cast(boundingSphere())) : + centerToEllipsoidSurface; + ellipsoidSurfaceOutDirection = glm::isnan(glm::length(ellipsoidSurfaceOutDirection)) ? + glm::dvec3(0.0, 1.0, 0.0) : ellipsoidSurfaceOutDirection; + + return { + centerToEllipsoidSurface, + ellipsoidSurfaceOutDirection, + heightToSurface + }; } void RenderableGlobe::setSaveCamera(std::shared_ptr camera) { diff --git a/modules/globebrowsing/globes/renderableglobe.h b/modules/globebrowsing/globes/renderableglobe.h index d4c5071577..73e080b2cc 100644 --- a/modules/globebrowsing/globes/renderableglobe.h +++ b/modules/globebrowsing/globes/renderableglobe.h @@ -107,7 +107,11 @@ public: double interactionDepthBelowEllipsoid(); // Setters - void setSaveCamera(std::shared_ptr camera); + void setSaveCamera(std::shared_ptr camera); + + virtual SurfacePositionHandle calculateSurfacePositionHandle( + const glm::dvec3& targetModelSpace) override; + private: // Globes. These are renderables inserted in a distance switch so that the heavier // ChunkedLodGlobe does not have to be rendered at far distances. @@ -119,7 +123,6 @@ private: DistanceSwitch _distanceSwitch; std::shared_ptr _savedCamera; - double _interactionDepthBelowEllipsoid; std::string _frame; double _time; diff --git a/modules/globebrowsing/rendering/chunkrenderer.cpp b/modules/globebrowsing/rendering/chunkrenderer.cpp index b8db58132e..f5c931878b 100644 --- a/modules/globebrowsing/rendering/chunkrenderer.cpp +++ b/modules/globebrowsing/rendering/chunkrenderer.cpp @@ -228,9 +228,9 @@ void ChunkRenderer::renderChunkGlobally(const Chunk& chunk, const RenderData& da programObject->setUniform("radiiSquared", glm::vec3(ellipsoid.radiiSquared())); if (_layerManager->layerGroup( - layergroupid::NightLayers).activeLayers().size() > 0 || + layergroupid::GroupID::NightLayers).activeLayers().size() > 0 || _layerManager->layerGroup( - layergroupid::WaterMasks).activeLayers().size() > 0 || + layergroupid::GroupID::WaterMasks).activeLayers().size() > 0 || chunk.owner().generalProperties().atmosphereEnabled || chunk.owner().generalProperties().performShading) { diff --git a/modules/globebrowsing/rendering/gpu/gpulayergroup.cpp b/modules/globebrowsing/rendering/gpu/gpulayergroup.cpp index 407dd8c7c3..12715dff59 100644 --- a/modules/globebrowsing/rendering/gpu/gpulayergroup.cpp +++ b/modules/globebrowsing/rendering/gpu/gpulayergroup.cpp @@ -58,7 +58,7 @@ void GPULayerGroup::bind(ghoul::opengl::ProgramObject* programObject, int pileSize = layerGroup.pileSize(); for (size_t i = 0; i < _gpuActiveLayers.size(); ++i) { // should maybe a proper GPULayer factory - _gpuActiveLayers[i] = (category == layergroupid::HeightLayers) ? + _gpuActiveLayers[i] = (category == layergroupid::GroupID::HeightLayers) ? std::make_unique() : std::make_unique(); std::string nameExtension = "[" + std::to_string(i) + "]."; diff --git a/modules/onscreengui/onscreenguimodule.cpp b/modules/onscreengui/onscreenguimodule.cpp index 397c47204e..af2b515b27 100644 --- a/modules/onscreengui/onscreenguimodule.cpp +++ b/modules/onscreengui/onscreenguimodule.cpp @@ -30,7 +30,7 @@ #include #include #include -#include +#include #include #include #include @@ -70,7 +70,7 @@ OnScreenGUIModule::OnScreenGUIModule() std::vector res = { &(OsEng.windowWrapper()), &(OsEng.settingsEngine()), - &(OsEng.interactionHandler()), + &(OsEng.navigationHandler()), &(OsEng.renderEngine()), &(OsEng.parallelConnection()), &(OsEng.console()) diff --git a/modules/onscreengui/src/guiorigincomponent.cpp b/modules/onscreengui/src/guiorigincomponent.cpp index 7c732583a8..79dfdc6036 100644 --- a/modules/onscreengui/src/guiorigincomponent.cpp +++ b/modules/onscreengui/src/guiorigincomponent.cpp @@ -25,7 +25,7 @@ #include #include -#include +#include #include #include #include @@ -42,7 +42,7 @@ GuiOriginComponent::GuiOriginComponent() {} void GuiOriginComponent::render() { - SceneGraphNode* currentFocus = OsEng.interactionHandler().focusNode(); + SceneGraphNode* currentFocus = OsEng.navigationHandler().focusNode(); std::vector nodes = OsEng.renderEngine().scene()->allSceneGraphNodes(); @@ -70,7 +70,7 @@ void GuiOriginComponent::render() { bool hasChanged = ImGui::Combo("Origin", ¤tPosition, nodeNames.c_str()); if (hasChanged) { OsEng.scriptEngine().queueScript( - "openspace.setPropertyValue('Interaction.origin', '" + + "openspace.setPropertyValue('NavigationHandler.origin', '" + nodes[currentPosition]->name() + "');", scripting::ScriptEngine::RemoteScripting::Yes ); diff --git a/modules/onscreengui/src/guiparallelcomponent.cpp b/modules/onscreengui/src/guiparallelcomponent.cpp index d865f7f0b1..b7435fdd53 100644 --- a/modules/onscreengui/src/guiparallelcomponent.cpp +++ b/modules/onscreengui/src/guiparallelcomponent.cpp @@ -26,7 +26,7 @@ #include #include -#include +#include #include #include @@ -83,7 +83,7 @@ void GuiParallelComponent::renderClientWithHost() { renderClientCommon(); const size_t nTimeKeyframes = OsEng.timeManager().nKeyframes(); - const size_t nCameraKeyframes = OsEng.interactionHandler().nKeyframes(); + const size_t nCameraKeyframes = OsEng.navigationHandler().keyframeNavigator().nKeyframes(); std::string timeKeyframeInfo = "TimeKeyframes : " + std::to_string(nTimeKeyframes); std::string cameraKeyframeInfo = "CameraKeyframes : " + std::to_string(nCameraKeyframes); diff --git a/modules/touch/include/TouchInteraction.h b/modules/touch/include/TouchInteraction.h index 9f6a736f45..194ebe55fc 100644 --- a/modules/touch/include/TouchInteraction.h +++ b/modules/touch/include/TouchInteraction.h @@ -31,8 +31,8 @@ #include #include #include -#include -#include +#include +#include #include #include #include diff --git a/modules/touch/src/TouchInteraction.cpp b/modules/touch/src/TouchInteraction.cpp index 135e7f0d44..6819db108a 100644 --- a/modules/touch/src/TouchInteraction.cpp +++ b/modules/touch/src/TouchInteraction.cpp @@ -25,7 +25,7 @@ #include #include -#include +#include #include #include #include @@ -530,7 +530,7 @@ void TouchInteraction::computeVelocities(const std::vector& list, co case PICK: { // pick something in the scene as focus node if (_selected.size() == 1 && _selected.at(0).node) { setFocusNode(_selected.at(0).node); - OsEng.interactionHandler().setFocusNode(_focusNode); // cant do setFocusNode() since TouchInteraction is not subclass of InteractionMode + OsEng.navigationHandler().setFocusNode(_focusNode); // cant do setFocusNode() since TouchInteraction is not subclass of InteractionMode // rotate camera to look at new focus, using slerp quat glm::dvec3 camToFocus = _focusNode->worldPosition() - _camera->positionVec3(); @@ -556,7 +556,7 @@ void TouchInteraction::computeVelocities(const std::vector& list, co void TouchInteraction::step(double dt) { using namespace glm; - setFocusNode(OsEng.interactionHandler().focusNode()); // since functions cant be called directly (TouchInteraction not a subclass of InteractionMode) + setFocusNode(OsEng.navigationHandler().focusNode()); // since functions cant be called directly (TouchInteraction not a subclass of InteractionMode) if (_focusNode && _camera) { // Create variables from current state dvec3 camPos = _camera->positionVec3(); @@ -776,4 +776,4 @@ void TouchInteraction::setFocusNode(SceneGraphNode* focusNode) { _focusNode = focusNode; } -} // openspace namespace \ No newline at end of file +} // openspace namespace diff --git a/modules/touch/src/TuioEar.cpp b/modules/touch/src/TuioEar.cpp index ae8e22efe9..90f1169fdf 100644 --- a/modules/touch/src/TuioEar.cpp +++ b/modules/touch/src/TuioEar.cpp @@ -27,7 +27,7 @@ #include #include #include -#include +#include #include #include @@ -156,4 +156,4 @@ TuioEar::TuioEar() { _tuioClient = new TuioClient(_oscReceiver); _tuioClient->addTuioListener(this); _tuioClient->connect(); -} \ No newline at end of file +} diff --git a/modules/touch/touchmodule.cpp b/modules/touch/touchmodule.cpp index 0d24b34aa6..663a4c2634 100644 --- a/modules/touch/touchmodule.cpp +++ b/modules/touch/touchmodule.cpp @@ -27,7 +27,7 @@ #include #include #include -#include +#include #include #include @@ -119,8 +119,8 @@ TouchModule::TouchModule() OsEng.registerModuleCallback( OpenSpaceEngine::CallbackOption::PreSync, [&]() { - touch.setCamera(OsEng.interactionHandler().camera()); - touch.setFocusNode(OsEng.interactionHandler().focusNode()); + touch.setCamera(OsEng.navigationHandler().camera()); + touch.setFocusNode(OsEng.navigationHandler().focusNode()); if (hasNewInput() && OsEng.windowWrapper().isMaster()) { touch.updateStateFromInput(listOfContactPoints, lastProcessed); diff --git a/scripts/common.lua b/scripts/common.lua index d44f11dea4..cf351cdaa7 100644 --- a/scripts/common.lua +++ b/scripts/common.lua @@ -52,18 +52,18 @@ helper.setCommonKeys = function() openspace.bindKey( "f", - helper.property.invert('Interaction.horizontalFriction'), + helper.property.invert('NavigationHandler.OrbitalNavigator.horizontalFriction'), "Toggles the horizontal friction of the camera. If it is disabled, the camera rotates around the focus object indefinitely." ) openspace.bindKey( "Shift+f", - helper.property.invert('Interaction.verticalFriction'), + helper.property.invert('NavigationHandler.OrbitalNavigator.verticalFriction'), "Toggles the vertical friction of the camera. If it is disabled, the camera rises up from or closes in towards the focus object indefinitely." ) openspace.bindKey( "Ctrl+f", - helper.property.invert('Interaction.rotationalFriction'), + helper.property.invert('NavigationHandler.OrbitalNavigator.rotationalFriction'), "Toggles the rotational friction of the camera. If it is disabled, the camera rotates around its own axis indefinitely." ) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0ba088f52b..478565e837 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -42,10 +42,15 @@ set(OPENSPACE_SOURCE ${OPENSPACE_BASE_DIR}/src/engine/wrapper/sgctwindowwrapper.cpp ${OPENSPACE_BASE_DIR}/src/engine/wrapper/windowwrapper.cpp ${OPENSPACE_BASE_DIR}/src/interaction/controller.cpp - ${OPENSPACE_BASE_DIR}/src/interaction/interactionhandler.cpp - ${OPENSPACE_BASE_DIR}/src/interaction/interactionmode.cpp - ${OPENSPACE_BASE_DIR}/src/interaction/interactionhandler_lua.inl + ${OPENSPACE_BASE_DIR}/src/interaction/inputstate.cpp + ${OPENSPACE_BASE_DIR}/src/interaction/keybindingmanager.cpp + ${OPENSPACE_BASE_DIR}/src/interaction/keybindingmanager_lua.inl + ${OPENSPACE_BASE_DIR}/src/interaction/keyframenavigator.cpp ${OPENSPACE_BASE_DIR}/src/interaction/luaconsole.cpp + ${OPENSPACE_BASE_DIR}/src/interaction/navigationhandler.cpp + ${OPENSPACE_BASE_DIR}/src/interaction/navigationhandler_lua.inl + ${OPENSPACE_BASE_DIR}/src/interaction/mousestate.cpp + ${OPENSPACE_BASE_DIR}/src/interaction/orbitalnavigator.cpp ${OPENSPACE_BASE_DIR}/src/mission/mission.cpp ${OPENSPACE_BASE_DIR}/src/mission/missionmanager.cpp ${OPENSPACE_BASE_DIR}/src/mission/missionmanager_lua.inl @@ -181,9 +186,17 @@ set(OPENSPACE_HEADER ${OPENSPACE_BASE_DIR}/include/openspace/engine/wrapper/sgctwindowwrapper.h ${OPENSPACE_BASE_DIR}/include/openspace/engine/wrapper/windowwrapper.h ${OPENSPACE_BASE_DIR}/include/openspace/interaction/controller.h - ${OPENSPACE_BASE_DIR}/include/openspace/interaction/interactionhandler.h - ${OPENSPACE_BASE_DIR}/include/openspace/interaction/interactionmode.h + ${OPENSPACE_BASE_DIR}/include/openspace/interaction/delayedvariable.h + ${OPENSPACE_BASE_DIR}/include/openspace/interaction/delayedvariable.inl + ${OPENSPACE_BASE_DIR}/include/openspace/interaction/inputstate.h + ${OPENSPACE_BASE_DIR}/include/openspace/interaction/interpolator.h + ${OPENSPACE_BASE_DIR}/include/openspace/interaction/interpolator.inl + ${OPENSPACE_BASE_DIR}/include/openspace/interaction/keybindingmanager.h + ${OPENSPACE_BASE_DIR}/include/openspace/interaction/keyframenavigator.h ${OPENSPACE_BASE_DIR}/include/openspace/interaction/luaconsole.h + ${OPENSPACE_BASE_DIR}/include/openspace/interaction/mousestate.h + ${OPENSPACE_BASE_DIR}/include/openspace/interaction/navigationhandler.h + ${OPENSPACE_BASE_DIR}/include/openspace/interaction/orbitalnavigator.h ${OPENSPACE_BASE_DIR}/include/openspace/mission/mission.h ${OPENSPACE_BASE_DIR}/include/openspace/mission/missionmanager.h ${OPENSPACE_BASE_DIR}/include/openspace/network/networkengine.h diff --git a/src/documentation/core_registration.cpp b/src/documentation/core_registration.cpp index 6d7dcba3bf..8539fe5b4e 100644 --- a/src/documentation/core_registration.cpp +++ b/src/documentation/core_registration.cpp @@ -30,7 +30,8 @@ #include #include #include -#include +#include +#include #include #include #include @@ -69,7 +70,8 @@ void registerCoreClasses(scripting::ScriptEngine& engine) { engine.addLibrary(RenderEngine::luaLibrary()); engine.addLibrary(Scene::luaLibrary()); engine.addLibrary(Time::luaLibrary()); - engine.addLibrary(interaction::InteractionHandler::luaLibrary()); + engine.addLibrary(interaction::KeyBindingManager::luaLibrary()); + engine.addLibrary(interaction::NavigationHandler::luaLibrary()); engine.addLibrary(ParallelConnection::luaLibrary()); engine.addLibrary(ModuleEngine::luaLibrary()); engine.addLibrary(scripting::ScriptScheduler::luaLibrary()); diff --git a/src/engine/openspaceengine.cpp b/src/engine/openspaceengine.cpp index 51149a9df4..35a8f0ba56 100644 --- a/src/engine/openspaceengine.cpp +++ b/src/engine/openspaceengine.cpp @@ -36,7 +36,8 @@ #include #include #include -#include +#include +#include #include #include #include @@ -140,7 +141,8 @@ OpenSpaceEngine::OpenSpaceEngine(std::string programName, , _commandlineParser(new ghoul::cmdparser::CommandlineParser( programName, ghoul::cmdparser::CommandlineParser::AllowUnknownCommands::Yes )) - , _interactionHandler(new interaction::InteractionHandler) + , _navigationHandler(new interaction::NavigationHandler) + , _keyBindingManager(new interaction::KeyBindingManager) , _scriptEngine(new scripting::ScriptEngine) , _scriptScheduler(new scripting::ScriptScheduler) , _virtualPropertyManager(new VirtualPropertyManager) @@ -151,10 +153,10 @@ OpenSpaceEngine::OpenSpaceEngine(std::string programName, , _shutdown({false, 0.f, 0.f}) , _isFirstRenderingFirstFrame(true) { - _interactionHandler->setPropertyOwner(_globalPropertyNamespace.get()); + _navigationHandler->setPropertyOwner(_globalPropertyNamespace.get()); // New property subowners also have to be added to the OnScreenGuiModule callback! - _globalPropertyNamespace->addPropertySubOwner(_interactionHandler.get()); + _globalPropertyNamespace->addPropertySubOwner(_navigationHandler.get()); _globalPropertyNamespace->addPropertySubOwner(_settingsEngine.get()); _globalPropertyNamespace->addPropertySubOwner(_renderEngine.get()); _globalPropertyNamespace->addPropertySubOwner(_windowWrapper.get()); @@ -206,6 +208,7 @@ void OpenSpaceEngine::create(int argc, char** argv, requestClose = false; LDEBUG("Initialize FileSystem"); + ghoul::initialize(); // Initialize the LogManager and add the console log as this will be used every time @@ -219,7 +222,6 @@ void OpenSpaceEngine::create(int argc, char** argv, ); LogMgr.addLog(std::make_unique()); - #ifdef __APPLE__ ghoul::filesystem::File app(argv[0]); std::string dirName = app.directoryName(); @@ -515,9 +517,9 @@ void OpenSpaceEngine::initialize() { _settingsEngine->initialize(); _settingsEngine->setModules(_moduleEngine->modules()); - // Initialize the InteractionHandler - _interactionHandler->initialize(); - + // Initialize the NavigationHandler + _navigationHandler->initialize(); + // Load a light and a monospaced font loadFonts(); @@ -601,7 +603,11 @@ void OpenSpaceEngine::loadScene(const std::string& scenePath) { _renderEngine->startFading(1, 3.0); scene->initialize(); - _interactionHandler->setCamera(scene->camera()); + // Update the scene so that position of objects are set in case they are used in + // post sync scripts + _renderEngine->updateScene(); + _navigationHandler->setCamera(scene->camera()); + _navigationHandler->setFocusNode(scene->camera()->parent()); try { runPostInitializationScripts(scenePath); @@ -612,7 +618,7 @@ void OpenSpaceEngine::loadScene(const std::string& scenePath) { // Write keyboard documentation. if (configurationManager().hasKey(ConfigurationManager::KeyKeyboardShortcuts)) { - interactionHandler().writeDocumentation( + keyBindingManager().writeDocumentation( absPath(configurationManager().value( ConfigurationManager::KeyKeyboardShortcuts )) @@ -638,7 +644,7 @@ void OpenSpaceEngine::loadScene(const std::string& scenePath) { void OpenSpaceEngine::deinitialize() { LTRACE("OpenSpaceEngine::deinitialize(begin)"); - _interactionHandler->deinitialize(); + _navigationHandler->deinitialize(); _renderEngine->deinitialize(); LTRACE("OpenSpaceEngine::deinitialize(end)"); @@ -1120,10 +1126,8 @@ void OpenSpaceEngine::preSynchronization() { ); } - _interactionHandler->updateInputStates(dt); - _renderEngine->updateScene(); - _interactionHandler->updateCamera(dt); + _navigationHandler->updateCamera(dt); _renderEngine->camera()->invalidateCache(); _parallelConnection->preSynchronization(); @@ -1158,9 +1162,6 @@ void OpenSpaceEngine::postSynchronizationPreDraw() { _renderEngine->camera()->invalidateCache(); } - // Step the camera using the current mouse velocities which are synced - //_interactionHandler->updateCamera(); - for (const auto& func : _moduleCallbacks.postSyncPreDraw) { func(); } @@ -1250,7 +1251,8 @@ void OpenSpaceEngine::keyboardCallback(Key key, KeyModifier mod, KeyAction actio return; } - _interactionHandler->keyboardCallback(key, mod, action); + _navigationHandler->keyboardCallback(key, mod, action); + _keyBindingManager->keyboardCallback(key, mod, action); } void OpenSpaceEngine::charCallback(unsigned int codepoint, KeyModifier modifier) { @@ -1272,7 +1274,7 @@ void OpenSpaceEngine::mouseButtonCallback(MouseButton button, MouseAction action } } - _interactionHandler->mouseButtonCallback(button, action); + _navigationHandler->mouseButtonCallback(button, action); } void OpenSpaceEngine::mousePositionCallback(double x, double y) { @@ -1280,7 +1282,7 @@ void OpenSpaceEngine::mousePositionCallback(double x, double y) { func(x, y); } - _interactionHandler->mousePositionCallback(x, y); + _navigationHandler->mousePositionCallback(x, y); } void OpenSpaceEngine::mouseScrollWheelCallback(double posX, double posY) { @@ -1291,7 +1293,7 @@ void OpenSpaceEngine::mouseScrollWheelCallback(double posX, double posY) { } } - _interactionHandler->mouseScrollWheelCallback(posY); + _navigationHandler->mouseScrollWheelCallback(posY); } void OpenSpaceEngine::encode() { @@ -1501,9 +1503,14 @@ ghoul::fontrendering::FontManager& OpenSpaceEngine::fontManager() { return *_fontManager; } -interaction::InteractionHandler& OpenSpaceEngine::interactionHandler() { - ghoul_assert(_interactionHandler, "InteractionHandler must not be nullptr"); - return *_interactionHandler; +interaction::NavigationHandler& OpenSpaceEngine::navigationHandler() { + ghoul_assert(_navigationHandler, "NavigationHandler must not be nullptr"); + return *_navigationHandler; +} + +interaction::KeyBindingManager& OpenSpaceEngine::keyBindingManager() { + ghoul_assert(_keyBindingManager, "KeyBindingManager must not be nullptr"); + return *_keyBindingManager; } properties::PropertyOwner& OpenSpaceEngine::globalPropertyOwner() { diff --git a/src/interaction/controller.cpp b/src/interaction/controller.cpp index 5e844f6a1d..a7bc2549ea 100644 --- a/src/interaction/controller.cpp +++ b/src/interaction/controller.cpp @@ -24,12 +24,12 @@ #include -#include +#include namespace openspace { namespace interaction { -void Controller::setHandler(InteractionHandler* handler) +void Controller::setHandler(NavigationHandler* handler) { _handler = handler; } diff --git a/src/interaction/inputstate.cpp b/src/interaction/inputstate.cpp new file mode 100644 index 0000000000..93c29b204c --- /dev/null +++ b/src/interaction/inputstate.cpp @@ -0,0 +1,97 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2017 * + * * + * 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 + +namespace openspace { +namespace interaction { + +void InputState::keyboardCallback(Key key, KeyModifier modifier, KeyAction action) { + if (action == KeyAction::Press) { + _keysDown.emplace_back(key, modifier); + } + else if (action == KeyAction::Release) { + // Remove all key pressings for 'key' + _keysDown.remove_if([key](std::pair keyModPair) + { return keyModPair.first == key; }); + } +} + +void InputState::mouseButtonCallback(MouseButton button, MouseAction action) { + if (action == MouseAction::Press) { + _mouseButtonsDown.push_back(button); + } + else if (action == MouseAction::Release) { + // Remove all key pressings for 'button' + _mouseButtonsDown.remove_if([button](MouseButton buttonInList) + { return button == buttonInList; }); + } +} + +void InputState::mousePositionCallback(double mouseX, double mouseY) { + _mousePosition = glm::dvec2(mouseX, mouseY); +} + +void InputState::mouseScrollWheelCallback(double mouseScrollDelta) { + _mouseScrollDelta = mouseScrollDelta; +} + +const std::list >& InputState::getPressedKeys() const { + return _keysDown; +} + +const std::list& InputState::getPressedMouseButtons() const { + return _mouseButtonsDown; +} + +glm::dvec2 InputState::getMousePosition() const { + return _mousePosition; +} + +double InputState::getMouseScrollDelta() const { + return _mouseScrollDelta; +} + +bool InputState::isKeyPressed(std::pair keyModPair) const { + return std::find(_keysDown.begin(), _keysDown.end(), keyModPair) != _keysDown.end(); +} + +bool InputState::isKeyPressed(Key key) const { + return std::find_if(_keysDown.begin(), _keysDown.end(), + [key](const std::pair& keyModPair) { + return key == keyModPair.first; + }) != _keysDown.end(); +} + +bool InputState::isMouseButtonPressed(MouseButton mouseButton) const { + return std::find(_mouseButtonsDown.begin(), _mouseButtonsDown.end(), + mouseButton) != _mouseButtonsDown.end(); +} + +} // namespace interaction +} // namespace openspace diff --git a/src/interaction/interactionhandler.cpp b/src/interaction/interactionhandler.cpp deleted file mode 100644 index 76b9817bd7..0000000000 --- a/src/interaction/interactionhandler.cpp +++ /dev/null @@ -1,563 +0,0 @@ -/***************************************************************************************** - * * - * OpenSpace * - * * - * Copyright (c) 2014-2017 * - * * - * 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 - -#ifdef OPENSPACE_MODULE_GLOBEBROWSING_ENABLED -#include -#endif - -#include - -#include - -namespace { - const char* _loggerCat = "InteractionHandler"; - - const char* KeyFocus = "Focus"; - const char* KeyPosition = "Position"; - const char* KeyRotation = "Rotation"; - - const char* MainTemplateFilename = "${OPENSPACE_DATA}/web/keybindings/main.hbs"; - const char* KeybindingTemplateFilename = "${OPENSPACE_DATA}/web/keybindings/keybinding.hbs"; - const char* JsFilename = "${OPENSPACE_DATA}/web/keybindings/script.js"; - - const int IdOrbitalInteractionMode = 0; - const char* KeyOrbitalInteractionMode = "Orbital"; - - const int IdGlobeBrowsingInteractionMode = 1; - const char* KeyGlobeBrowsingInteractionMode = "GlobeBrowsing"; - - const int IdKeyframeInteractionMode = 2; - const char* KeyKeyframeInteractionMode = "Keyframe"; -} // namespace - -#include "interactionhandler_lua.inl" - -namespace openspace { -namespace interaction { - -// InteractionHandler -InteractionHandler::InteractionHandler() - : properties::PropertyOwner("Interaction") - , DocumentationGenerator( - "Documentation", - "keybindings", - { - { "keybindingTemplate", KeybindingTemplateFilename }, - { "mainTemplate", MainTemplateFilename } - }, - JsFilename - ) - , _origin("origin", "Origin", "") - , _rotationalFriction("rotationalFriction", "Rotational Friction", true) - , _horizontalFriction("horizontalFriction", "Horizontal Friction", true) - , _verticalFriction("verticalFriction", "Vertical Friction", true) - , _sensitivity("sensitivity", "Sensitivity", 0.5f, 0.001f, 1.f) - , _rapidness("rapidness", "Rapidness", 1.f, 0.1f, 60.f) - , _interactionModeOption( - "interactionMode", - "Interaction Mode", - properties::OptionProperty::DisplayType::Dropdown - ) -{ - _origin.onChange([this]() { - SceneGraphNode* node = sceneGraphNode(_origin.value()); - if (!node) { - LWARNING("Could not find a node in scenegraph called '" << _origin.value() << "'"); - return; - } - setFocusNode(node); - resetCameraDirection(); - }); - - // Create the interactionModes - _inputState = std::make_unique(); - // Inject the same mouse states to both orbital and global interaction mode - _mouseStates = std::make_unique(_sensitivity * pow(10.0,-4), 1); - - _orbitalInteractionMode = std::make_unique(_mouseStates); - _globeBrowsingInteractionMode = std::make_unique(_mouseStates); - _keyframeInteractionMode = std::make_unique(); - - _interactionModeOption.addOption(IdOrbitalInteractionMode, KeyOrbitalInteractionMode); - _interactionModeOption.addOption(IdGlobeBrowsingInteractionMode, KeyGlobeBrowsingInteractionMode); - _interactionModeOption.addOption(IdKeyframeInteractionMode, KeyKeyframeInteractionMode); - - // Set the interactionMode - _currentInteractionMode = _orbitalInteractionMode.get(); - - // Define lambda functions for changed properties - _rotationalFriction.onChange([&]() { - _mouseStates->setRotationalFriction(_rotationalFriction); - }); - _horizontalFriction.onChange([&]() { - _mouseStates->setHorizontalFriction(_horizontalFriction); - }); - _verticalFriction.onChange([&]() { - _mouseStates->setVerticalFriction(_verticalFriction); - }); - _sensitivity.onChange([&]() { - _mouseStates->setSensitivity(_sensitivity * pow(10.0,-4)); - }); - _rapidness.onChange([&]() { - _mouseStates->setVelocityScaleFactor(_rapidness); - }); - - _interactionModeOption.onChange([this]() { - switch(_interactionModeOption.value()) { - case IdGlobeBrowsingInteractionMode: - setInteractionMode(_globeBrowsingInteractionMode.get()); - break; - case IdKeyframeInteractionMode: - setInteractionMode(_keyframeInteractionMode.get()); - break; - case IdOrbitalInteractionMode: - default: - setInteractionMode(_orbitalInteractionMode.get()); - break; - } - }); - - - // Add the properties - addProperty(_origin); - addProperty(_interactionModeOption); - - addProperty(_rotationalFriction); - addProperty(_horizontalFriction); - addProperty(_verticalFriction); - addProperty(_sensitivity); - addProperty(_rapidness); -} - -InteractionHandler::~InteractionHandler() { - -} - -void InteractionHandler::initialize() { - OsEng.parallelConnection().connectionEvent()->subscribe("interactionHandler", "statusChanged", [this]() { - if (OsEng.parallelConnection().status() == ParallelConnection::Status::ClientWithHost) { - setInteractionMode(_keyframeInteractionMode.get()); - } else if (_currentInteractionMode == _keyframeInteractionMode.get()) { - setInteractionMode(_orbitalInteractionMode.get()); - } - }); -} - -void InteractionHandler::deinitialize() { - OsEng.parallelConnection().connectionEvent()->unsubscribe("interactionHandler"); -} - -void InteractionHandler::setFocusNode(SceneGraphNode* node) { - _currentInteractionMode->setFocusNode(node); -} - -void InteractionHandler::setCamera(Camera* camera) { - _camera = camera; - setFocusNode(_camera->parent()); -} - -void InteractionHandler::resetCameraDirection() { - LINFO("Setting camera direction to point at focus node."); - _currentInteractionMode->rotateToFocusNodeInterpolator().start(); -} - -void InteractionHandler::setInteractionMode(InteractionMode* interactionMode) { - // Focus node is passed over from the previous interaction mode - SceneGraphNode* focusNode = _currentInteractionMode->focusNode(); - - // Set the interaction mode - _currentInteractionMode = interactionMode; - - // Update the focusnode for the new interaction mode - _currentInteractionMode->setFocusNode(focusNode); -} - -InteractionMode * InteractionHandler::interactionMode() { - return _currentInteractionMode; -} - -void InteractionHandler::goToChunk(int x, int y, int level) { - if (_currentInteractionMode == _globeBrowsingInteractionMode.get()) { -#ifdef OPENSPACE_MODULE_GLOBEBROWSING_ENABLED - _globeBrowsingInteractionMode->goToChunk(*_camera, globebrowsing::TileIndex(x,y,level), glm::vec2(0.5,0.5), true); -#endif - } else { - LWARNING("Interaction mode must be set to 'GlobeBrowsing'"); - } -} - -void InteractionHandler::goToGeo(double latitude, double longitude) { - if (_currentInteractionMode == _globeBrowsingInteractionMode.get()) { -#ifdef OPENSPACE_MODULE_GLOBEBROWSING_ENABLED - _globeBrowsingInteractionMode->goToGeodetic2( - *_camera, - globebrowsing::Geodetic2(latitude, longitude) / 180 * glm::pi(), true - ); -#endif - } else { - LWARNING("Interaction mode must be set to 'GlobeBrowsing'"); - } -} - -void InteractionHandler::lockControls() { - -} - -void InteractionHandler::unlockControls() { - -} - -void InteractionHandler::updateInputStates(double timeSinceLastUpdate) { - ghoul_assert(_inputState != nullptr, "InputState cannot be null!"); - ghoul_assert(_camera != nullptr, "Camera cannot be null!"); - _currentInteractionMode->updateMouseStatesFromInput(*_inputState, timeSinceLastUpdate); -} - -void InteractionHandler::updateCamera(double deltaTime) { - ghoul_assert(_inputState != nullptr, "InputState cannot be null!"); - ghoul_assert(_camera != nullptr, "Camera cannot be null!"); - - if (_cameraUpdatedFromScript) { - _cameraUpdatedFromScript = false; - } - else { - if (_camera && focusNode()) { - _currentInteractionMode->updateCameraStateFromMouseStates(*_camera, deltaTime); - _camera->setFocusPositionVec3(focusNode()->worldPosition()); - } - } -} - -SceneGraphNode* InteractionHandler::focusNode() const { - return _currentInteractionMode->focusNode(); -} - -glm::dvec3 InteractionHandler::focusNodeToCameraVector() const { - return _camera->positionVec3() - focusNode()->worldPosition(); -} - -glm::quat InteractionHandler::focusNodeToCameraRotation() const { - glm::dmat4 invWorldRotation = glm::inverse(focusNode()->worldRotationMatrix()); - return glm::quat(invWorldRotation) * glm::quat(_camera->rotationQuaternion()); -} - -Camera* InteractionHandler::camera() const { - return _camera; -} - -const InputState& InteractionHandler::inputState() const { - return *_inputState; -} - - -void InteractionHandler::mouseButtonCallback(MouseButton button, MouseAction action) { - _inputState->mouseButtonCallback(button, action); -} - -void InteractionHandler::mousePositionCallback(double x, double y) { - _inputState->mousePositionCallback(x, y); -} - -void InteractionHandler::mouseScrollWheelCallback(double pos) { - _inputState->mouseScrollWheelCallback(pos); -} - -void InteractionHandler::keyboardCallback(Key key, KeyModifier modifier, KeyAction action) { - _inputState->keyboardCallback(key, modifier, action); - - if (action == KeyAction::Press || action == KeyAction::Repeat) { - // iterate over key bindings - auto ret = _keyLua.equal_range({ key, modifier }); - for (auto it = ret.first; it != ret.second; ++it) { - auto remote = it->second.synchronization ? - scripting::ScriptEngine::RemoteScripting::Yes : - scripting::ScriptEngine::RemoteScripting::No; - - OsEng.scriptEngine().queueScript(it->second.command, remote); - } - } -} - -void InteractionHandler::setCameraStateFromDictionary(const ghoul::Dictionary& cameraDict) { - bool readSuccessful = true; - - std::string focus; - glm::dvec3 cameraPosition; - glm::dvec4 cameraRotation; // Need to read the quaternion as a vector first. - - readSuccessful &= cameraDict.getValue(KeyFocus, focus); - readSuccessful &= cameraDict.getValue(KeyPosition, cameraPosition); - readSuccessful &= cameraDict.getValue(KeyRotation, cameraRotation); - - if (!readSuccessful) { - throw ghoul::RuntimeError( - "Position, Rotation and Focus need to be defined for camera dictionary."); - } - - SceneGraphNode* node = sceneGraphNode(focus); - if (!node) { - throw ghoul::RuntimeError( - "Could not find a node in scenegraph called '" + focus + "'"); - } - - // Set state - setFocusNode(node); - _camera->setPositionVec3(cameraPosition); - _camera->setRotation(glm::dquat( - cameraRotation.x, cameraRotation.y, cameraRotation.z, cameraRotation.w)); -} - -ghoul::Dictionary InteractionHandler::getCameraStateDictionary() { - glm::dvec3 cameraPosition; - glm::dquat quat; - glm::dvec4 cameraRotation; - - cameraPosition = _camera->positionVec3(); - quat = _camera->rotationQuaternion(); - cameraRotation = glm::dvec4(quat.w, quat.x, quat.y, quat.z); - - ghoul::Dictionary cameraDict; - cameraDict.setValue(KeyPosition, cameraPosition); - cameraDict.setValue(KeyRotation, cameraRotation); - cameraDict.setValue(KeyFocus, focusNode()->name()); - - return cameraDict; -} - -void InteractionHandler::saveCameraStateToFile(const std::string& filepath) { - if (!filepath.empty()) { - auto fullpath = absPath(filepath); - LINFO("Saving camera position: " << filepath); - - ghoul::Dictionary cameraDict = getCameraStateDictionary(); - - // TODO : Should get the camera state as a dictionary and save the dictionary to - // a file in form of a lua state and not use ofstreams here. - - std::ofstream ofs(fullpath.c_str()); - - glm::dvec3 p = _camera->positionVec3(); - glm::dquat q = _camera->rotationQuaternion(); - - ofs << "return {" << std::endl; - ofs << " " << KeyFocus << " = " << "\"" << focusNode()->name() << "\"" << "," << std::endl; - ofs << " " << KeyPosition << " = {" - << std::to_string(p.x) << ", " - << std::to_string(p.y) << ", " - << std::to_string(p.z) << "}," << std::endl; - ofs << " " << KeyRotation << " = {" - << std::to_string(q.w) << ", " - << std::to_string(q.x) << ", " - << std::to_string(q.y) << ", " - << std::to_string(q.z) << "}," << std::endl; - ofs << "}"<< std::endl; - - ofs.close(); - } -} - -void InteractionHandler::restoreCameraStateFromFile(const std::string& filepath) { - LINFO("Reading camera state from file: " << filepath); - if (!FileSys.fileExists(filepath)) - throw ghoul::FileNotFoundError(filepath, "CameraFilePath"); - - ghoul::Dictionary cameraDict; - try { - ghoul::lua::loadDictionaryFromFile(filepath, cameraDict); - setCameraStateFromDictionary(cameraDict); - _cameraUpdatedFromScript = true; - } - catch (ghoul::RuntimeError& e) { - LWARNING("Unable to set camera position"); - LWARNING(e.message); - } -} - -void InteractionHandler::resetKeyBindings() { - _keyLua.clear(); -} - -void InteractionHandler::bindKeyLocal(Key key, KeyModifier modifier, - std::string luaCommand, std::string documentation) -{ - _keyLua.insert({ - { key, modifier }, - { std::move(luaCommand), Synchronized::No, std::move(documentation) } - }); -} - -void InteractionHandler::bindKey(Key key, KeyModifier modifier, - std::string luaCommand, std::string documentation) -{ - _keyLua.insert({ - { key, modifier }, - { std::move(luaCommand), Synchronized::Yes, std::move(documentation) } - }); -} - - -std::string InteractionHandler::generateJson() const { - std::stringstream json; - json << "["; - bool first = true; - for (const auto& p : _keyLua) { - if (!first) { - json << ","; - } - first = false; - json << "{"; - json << "\"key\": \"" << std::to_string(p.first) << "\","; - json << "\"script\": \"" << p.second.command << "\","; - json << "\"remoteScripting\": " << (p.second.synchronization ? "true," : "false,"); - json << "\"documentation\": \"" << p.second.documentation << "\""; - json << "}"; - } - json << "]"; - - std::string jsonString = ""; - for (const char& c : json.str()) { - if (c == '\'') { - jsonString += "\\'"; - } else { - jsonString += c; - } - } - - return jsonString; -} - - -scripting::LuaLibrary InteractionHandler::luaLibrary() { - return{ - "", - { - { - "clearKeys", - &luascriptfunctions::clearKeys, - "", - "Clear all key bindings" - }, - { - "bindKey", - &luascriptfunctions::bindKey, - "string, string [,string]", - "Binds a key by name to a lua string command to execute both locally " - "and to broadcast to clients if this is the host of a parallel session. " - "The first argument is the key, the second argument is the Lua command " - "that is to be executed, and the optional third argument is a human " - "readable description of the command for documentation purposes." - }, - { - "bindKeyLocal", - &luascriptfunctions::bindKeyLocal, - "string, string [,string]", - "Binds a key by name to a lua string command to execute only locally. " - "The first argument is the key, the second argument is the Lua command " - "that is to be executed, and the optional third argument is a human " - "readable description of the command for documentation purposes." - }, - { - "saveCameraStateToFile", - &luascriptfunctions::saveCameraStateToFile, - "string", - "Save the current camera state to file" - }, - { - "restoreCameraStateFromFile", - &luascriptfunctions::restoreCameraStateFromFile, - "string", - "Restore the camera state from file" - }, - { - "resetCameraDirection", - &luascriptfunctions::resetCameraDirection, - "void", - "Reset the camera direction to point at the focus node" - }, - { - "goToChunk", - &luascriptfunctions::goToChunk, - "void", - "Go to chunk with given index x, y, level" - }, - { - "goToGeo", - &luascriptfunctions::goToGeo, - "void", - "Go to geographic coordinates latitude and longitude" - }, - } - }; -} - -void InteractionHandler::addKeyframe(double timestamp, KeyframeInteractionMode::CameraPose pose) { - if (!_keyframeInteractionMode) { - return; - } - _keyframeInteractionMode->timeline().addKeyframe(timestamp, pose); -} - -void InteractionHandler::removeKeyframesAfter(double timestamp) { - if (!_keyframeInteractionMode) { - return; - } - _keyframeInteractionMode->timeline().removeKeyframesAfter(timestamp); -} - -void InteractionHandler::clearKeyframes() { - if (!_keyframeInteractionMode) { - return; - } - _keyframeInteractionMode->timeline().clearKeyframes(); -} - -size_t InteractionHandler::nKeyframes() const { - if (!_keyframeInteractionMode) { - return 0; - } - return _keyframeInteractionMode->timeline().keyframes().size(); -} - -} // namespace interaction -} // namespace openspace diff --git a/src/interaction/interactionmode.cpp b/src/interaction/interactionmode.cpp deleted file mode 100644 index 1c487230ec..0000000000 --- a/src/interaction/interactionmode.cpp +++ /dev/null @@ -1,800 +0,0 @@ -/***************************************************************************************** - * * - * OpenSpace * - * * - * Copyright (c) 2014-2017 * - * * - * 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 - -#ifdef OPENSPACE_MODULE_GLOBEBROWSING_ENABLED -#include -#include -#include -#endif - -#include - -namespace { - const std::string _loggerCat = "InteractionMode"; -} - -namespace openspace { -namespace interaction { - - // InputState - InputState::InputState() { - - } - - InputState::~InputState() { - - } - - void InputState::keyboardCallback(Key key, KeyModifier modifier, KeyAction action) { - if (action == KeyAction::Press) { - _keysDown.push_back(std::pair(key, modifier)); - } - else if (action == KeyAction::Release) { - // Remove all key pressings for 'key' - _keysDown.remove_if([key](std::pair keyModPair) - { return keyModPair.first == key; }); - } - } - - void InputState::mouseButtonCallback(MouseButton button, MouseAction action) { - if (action == MouseAction::Press) { - _mouseButtonsDown.push_back(button); - } - else if (action == MouseAction::Release) { - // Remove all key pressings for 'button' - _mouseButtonsDown.remove_if([button](MouseButton buttonInList) - { return button == buttonInList; }); - } - } - - void InputState::mousePositionCallback(double mouseX, double mouseY) { - _mousePosition = glm::dvec2(mouseX, mouseY); - } - - void InputState::mouseScrollWheelCallback(double mouseScrollDelta) { - _mouseScrollDelta = mouseScrollDelta; - } - - const std::list >& InputState::getPressedKeys() const { - return _keysDown; - } - - const std::list& InputState::getPressedMouseButtons() const { - return _mouseButtonsDown; - } - - glm::dvec2 InputState::getMousePosition() const { - return _mousePosition; - } - - double InputState::getMouseScrollDelta() const { - return _mouseScrollDelta; - } - - bool InputState::isKeyPressed(std::pair keyModPair) const { - for (auto it = _keysDown.begin(); it != _keysDown.end(); it++) { - if (*it == keyModPair) { - return true; - } - } - return false; - } - - bool InputState::isKeyPressed(Key key) const { - for (auto it = _keysDown.begin(); it != _keysDown.end(); it++) { - if ((*it).first == key) { - return true; - } - } - return false; - } - - bool InputState::isMouseButtonPressed(MouseButton mouseButton) const { - for (auto it = _mouseButtonsDown.begin(); it != _mouseButtonsDown.end(); it++) { - if (*it == mouseButton) { - return true; - } - } - return false; - } - - - -InteractionMode::InteractionMode() - : _rotateToFocusNodeInterpolator(Interpolator([](double t){ - return pow(t, 2); - })) { -} - - -InteractionMode::~InteractionMode() { - -} - -void InteractionMode::setFocusNode(SceneGraphNode* focusNode) { - _focusNode = focusNode; - - if (_focusNode != nullptr) { - _previousFocusNodePosition = _focusNode->worldPosition(); - _previousFocusNodeRotation = glm::quat_cast(_focusNode->worldRotationMatrix()); - } -} - -SceneGraphNode* InteractionMode::focusNode() { - return _focusNode; -} - -Interpolator& InteractionMode::rotateToFocusNodeInterpolator() { - return _rotateToFocusNodeInterpolator; -}; - - -// KeyframeInteractionMode -KeyframeInteractionMode::KeyframeInteractionMode() -{ -} - -KeyframeInteractionMode::~KeyframeInteractionMode() { -} - - -void KeyframeInteractionMode::updateMouseStatesFromInput(const InputState&, double) { - // Do nothing. -} - -void KeyframeInteractionMode::updateCameraStateFromMouseStates(Camera& camera, double) { - double now = OsEng.runTime(); - - if (_cameraPoseTimeline.nKeyframes() == 0) { - return; - } - - const Keyframe* nextKeyframe = _cameraPoseTimeline.firstKeyframeAfter(now); - const Keyframe* prevKeyframe = _cameraPoseTimeline.lastKeyframeBefore(now); - double nextTime = 0; - double prevTime = 0; - double t = 0; - - if (nextKeyframe) { - nextTime = nextKeyframe->timestamp; - } else { - return; - } - - if (prevKeyframe) { - prevTime = prevKeyframe->timestamp; - t = (now - prevTime) / (nextTime - prevTime); - } else { - // If there is no keyframe before: Only use the next keyframe. - prevTime = nextTime; - prevKeyframe = nextKeyframe; - t = 1; - } - - _cameraPoseTimeline.removeKeyframesBefore(prevTime); - - const CameraPose& prevPose = prevKeyframe->data; - const CameraPose& nextPose = nextKeyframe->data; - - Scene* scene = camera.parent()->scene(); - SceneGraphNode* prevFocusNode = scene->sceneGraphNode(prevPose.focusNode); - SceneGraphNode* nextFocusNode = scene->sceneGraphNode(nextPose.focusNode); - - if (!prevFocusNode || !nextFocusNode) { - return; - } - - glm::dvec3 prevKeyframeCameraPosition = prevPose.position; - glm::dvec3 nextKeyframeCameraPosition = nextPose.position; - glm::dquat prevKeyframeCameraRotation = prevPose.rotation; - glm::dquat nextKeyframeCameraRotation = nextPose.rotation; - - // Transform position and rotation based on focus node rotation (if following rotation) - if (prevPose.followFocusNodeRotation) { - prevKeyframeCameraRotation = prevFocusNode->worldRotationMatrix() * glm::dmat3(glm::dquat(prevPose.rotation)); - prevKeyframeCameraPosition = prevFocusNode->worldRotationMatrix() * prevPose.position; - } - if (nextPose.followFocusNodeRotation) { - nextKeyframeCameraRotation = nextFocusNode->worldRotationMatrix() * glm::dmat3(glm::dquat(nextPose.rotation)); - nextKeyframeCameraPosition = nextFocusNode->worldRotationMatrix() * nextPose.position; - } - - // Transform position based on focus node position - prevKeyframeCameraPosition += prevFocusNode->worldPosition(); - nextKeyframeCameraPosition += nextFocusNode->worldPosition(); - - // Linear interpolation - camera.setPositionVec3(prevKeyframeCameraPosition * (1 - t) + nextKeyframeCameraPosition * t); - camera.setRotation(glm::slerp(prevKeyframeCameraRotation, nextKeyframeCameraRotation, t)); -} - -bool KeyframeInteractionMode::followingNodeRotation() const { - return false; -} - -Timeline& KeyframeInteractionMode::timeline() { - return _cameraPoseTimeline; -} - -// OrbitalInteractionMode -OrbitalInteractionMode::MouseStates::MouseStates(double sensitivity, double velocityScaleFactor) - : _sensitivity(sensitivity) - , _globalRotationMouseState(velocityScaleFactor) - , _localRotationMouseState(velocityScaleFactor) - , _truckMovementMouseState(velocityScaleFactor) - , _localRollMouseState(velocityScaleFactor) - , _globalRollMouseState(velocityScaleFactor) -{} - -void OrbitalInteractionMode::MouseStates::updateMouseStatesFromInput(const InputState& inputState, double deltaTime) { -#ifdef OPENSPACE_MODULE_TOUCH_ENABLED - ghoul::any t = property("Global Properties.Touch.TouchInteraction.TouchEvents")->get(); - if (!*(ghoul::any_cast(&t))) { -#endif // OPENSPACE_MODULE_GLOBEBROWSING_ENABLED - glm::dvec2 mousePosition = inputState.getMousePosition(); - - bool button1Pressed = inputState.isMouseButtonPressed(MouseButton::Button1); - bool button2Pressed = inputState.isMouseButtonPressed(MouseButton::Button2); - bool button3Pressed = inputState.isMouseButtonPressed(MouseButton::Button3); - bool keyCtrlPressed = inputState.isKeyPressed(Key::LeftControl); - bool keyShiftPressed = inputState.isKeyPressed(Key::LeftShift); - - // Update the mouse states - if (button1Pressed && !keyShiftPressed) { - if (keyCtrlPressed) { - glm::dvec2 mousePositionDelta = - _localRotationMouseState.previousPosition - mousePosition; - _localRotationMouseState.velocity.set(mousePositionDelta * _sensitivity, deltaTime); - - _globalRotationMouseState.previousPosition = mousePosition; - _globalRotationMouseState.velocity.decelerate(deltaTime); - } - else { - glm::dvec2 mousePositionDelta = - _globalRotationMouseState.previousPosition - mousePosition; - _globalRotationMouseState.velocity.set(mousePositionDelta * _sensitivity, deltaTime); - - _localRotationMouseState.previousPosition = mousePosition; - _localRotationMouseState.velocity.decelerate(deltaTime); - } - } - else { // !button1Pressed - _localRotationMouseState.previousPosition = mousePosition; - _localRotationMouseState.velocity.decelerate(deltaTime); - - _globalRotationMouseState.previousPosition = mousePosition; - _globalRotationMouseState.velocity.decelerate(deltaTime); - } - if (button2Pressed) { - glm::dvec2 mousePositionDelta = - _truckMovementMouseState.previousPosition - mousePosition; - _truckMovementMouseState.velocity.set(mousePositionDelta * _sensitivity, deltaTime); - } - else { // !button2Pressed - _truckMovementMouseState.previousPosition = mousePosition; - _truckMovementMouseState.velocity.decelerate(deltaTime); - } - if (button3Pressed || (keyShiftPressed && button1Pressed)) { - if (keyCtrlPressed) { - glm::dvec2 mousePositionDelta = - _localRollMouseState.previousPosition - mousePosition; - _localRollMouseState.velocity.set(mousePositionDelta * _sensitivity, deltaTime); - - _globalRollMouseState.previousPosition = mousePosition; - _globalRollMouseState.velocity.decelerate(deltaTime); - } - else { - glm::dvec2 mousePositionDelta = - _globalRollMouseState.previousPosition - mousePosition; - _globalRollMouseState.velocity.set(mousePositionDelta * _sensitivity, deltaTime); - - _localRollMouseState.previousPosition = mousePosition; - _localRollMouseState.velocity.decelerate(deltaTime); - } - } - else { // !button3Pressed - _globalRollMouseState.previousPosition = mousePosition; - _globalRollMouseState.velocity.decelerate(deltaTime); - - _localRollMouseState.previousPosition = mousePosition; - _localRollMouseState.velocity.decelerate(deltaTime); - } -#ifdef OPENSPACE_MODULE_TOUCH_ENABLED - } -#endif // OPENSPACE_MODULE_TOUCH_ENABLED - -} - -void OrbitalInteractionMode::MouseStates::setRotationalFriction(double friction) { - _localRotationMouseState.setFriction(friction); - _localRollMouseState.setFriction(friction); - _globalRollMouseState.setFriction(friction); -} - -void OrbitalInteractionMode::MouseStates::setHorizontalFriction(double friction) { - _globalRotationMouseState.setFriction(friction); -} - -void OrbitalInteractionMode::MouseStates::setVerticalFriction(double friction) { - _truckMovementMouseState.setFriction(friction); -} - -void OrbitalInteractionMode::MouseStates::setSensitivity(double sensitivity) { - _sensitivity = sensitivity; -} - -void OrbitalInteractionMode::MouseStates::setVelocityScaleFactor(double scaleFactor) { - _globalRotationMouseState.setVelocityScaleFactor(scaleFactor); - _localRotationMouseState.setVelocityScaleFactor(scaleFactor); - _truckMovementMouseState.setVelocityScaleFactor(scaleFactor); - _localRollMouseState.setVelocityScaleFactor(scaleFactor); - _globalRollMouseState.setVelocityScaleFactor(scaleFactor); -} - -glm::dvec2 OrbitalInteractionMode::MouseStates::synchedGlobalRotationMouseVelocity() { - return _globalRotationMouseState.velocity.get(); -} - -glm::dvec2 OrbitalInteractionMode::MouseStates::synchedLocalRotationMouseVelocity() { - return _localRotationMouseState.velocity.get(); -} - -glm::dvec2 OrbitalInteractionMode::MouseStates::synchedTruckMovementMouseVelocity() { - return _truckMovementMouseState.velocity.get(); -} - -glm::dvec2 OrbitalInteractionMode::MouseStates::synchedLocalRollMouseVelocity() { - return _localRollMouseState.velocity.get(); -} - -glm::dvec2 OrbitalInteractionMode::MouseStates::synchedGlobalRollMouseVelocity() { - return _globalRollMouseState.velocity.get(); -} - -OrbitalInteractionMode::OrbitalInteractionMode( - std::shared_ptr mouseStates) - : InteractionMode() - , _mouseStates(mouseStates) { - -} - -OrbitalInteractionMode::~OrbitalInteractionMode() { - -} - -void OrbitalInteractionMode::updateCameraStateFromMouseStates(Camera& camera, double deltaTime) { - // Update synched data - - using namespace glm; - if (_focusNode) { - // Read the current state of the camera and focus node - dvec3 camPos = camera.positionVec3(); - - // Follow focus nodes movement - dvec3 centerPos = _focusNode->worldPosition(); - dvec3 focusNodeDiff = centerPos - _previousFocusNodePosition; - _previousFocusNodePosition = centerPos; - camPos += focusNodeDiff; - - dquat totalRotation = camera.rotationQuaternion(); - dvec3 directionToCenter = normalize(centerPos - camPos); - dvec3 lookUp = camera.lookUpVectorWorldSpace(); - double boundingSphere = _focusNode->boundingSphere(); - dvec3 camDirection = camera.viewDirectionWorldSpace(); - - // Declare other variables used in interaction calculations - double minHeightAboveBoundingSphere = 1; - dvec3 centerToCamera = camPos - centerPos; - dvec3 centerToBoundingSphere; - - // Create the internal representation of the local and global camera rotations - dmat4 lookAtMat = lookAt( - dvec3(0, 0, 0), - directionToCenter, - normalize(camDirection + lookUp)); // To avoid problem with lookup in up direction - dquat globalCameraRotation = normalize(quat_cast(inverse(lookAtMat))); - dquat localCameraRotation = inverse(globalCameraRotation) * totalRotation; - - { // Do local roll - glm::dquat cameraRollRotation = - glm::angleAxis(_mouseStates->synchedLocalRollMouseVelocity().x, dvec3(0, 0, 1)); - localCameraRotation = localCameraRotation * cameraRollRotation; - } - if (!_rotateToFocusNodeInterpolator.isInterpolating()) - { // Do local rotation - dvec3 eulerAngles(_mouseStates->synchedLocalRotationMouseVelocity().y, _mouseStates->synchedLocalRotationMouseVelocity().x, 0); - dquat rotationDiff = dquat(eulerAngles); - - localCameraRotation = localCameraRotation * rotationDiff; - } - else - { // Interpolate local rotation to focus node - double t = _rotateToFocusNodeInterpolator.value(); - localCameraRotation = slerp(localCameraRotation, dquat(dvec3(0.0)), t); - _rotateToFocusNodeInterpolator.step(deltaTime * 0.1); // Should probably depend on dt - // This is a fast but ugly solution to slow regaining of control... - if (t > 0.18) { - _rotateToFocusNodeInterpolator.end(); - } - } - { // Do global rotation - dvec2 smoothMouseVelocity = _mouseStates->synchedGlobalRotationMouseVelocity(); - dvec3 eulerAngles(-smoothMouseVelocity.y, -smoothMouseVelocity.x, 0); - dquat rotationDiffCamSpace = dquat(eulerAngles); - - dquat newRotationCamspace = globalCameraRotation * rotationDiffCamSpace; - dquat rotationDiffWorldSpace = newRotationCamspace * inverse(globalCameraRotation); - dvec3 rotationDiffVec3 = centerToCamera * rotationDiffWorldSpace - centerToCamera; - - camPos += rotationDiffVec3; - dvec3 centerToCamera = camPos - centerPos; - directionToCenter = normalize(-centerToCamera); - - dvec3 lookUpWhenFacingCenter = - globalCameraRotation * dvec3(camera.lookUpVectorCameraSpace()); - dmat4 lookAtMat = lookAt( - dvec3(0, 0, 0), - directionToCenter, - lookUpWhenFacingCenter); - globalCameraRotation = normalize(quat_cast(inverse(lookAtMat))); - } - { // Move position towards or away from focus node - centerToBoundingSphere = - -directionToCenter * - boundingSphere; - camPos += -(centerToCamera - centerToBoundingSphere) * - _mouseStates->synchedTruckMovementMouseVelocity().y; - } - { // Roll around sphere normal - dquat cameraRollRotation = - angleAxis(_mouseStates->synchedGlobalRollMouseVelocity().x, -directionToCenter); - globalCameraRotation = cameraRollRotation * globalCameraRotation; - } - { // Push up to surface - dvec3 sphereSurfaceToCamera = camPos - (centerPos + centerToBoundingSphere); - - double distFromSphereSurfaceToCamera = length(sphereSurfaceToCamera); - camPos += -directionToCenter * - max(minHeightAboveBoundingSphere - distFromSphereSurfaceToCamera, 0.0); - } - - // Update the camera state - camera.setPositionVec3(camPos); - camera.setRotation(globalCameraRotation * localCameraRotation); - } -} - -bool OrbitalInteractionMode::followingNodeRotation() const { - return false; -} - -void OrbitalInteractionMode::updateMouseStatesFromInput(const InputState& inputState, double deltaTime) { - _mouseStates->updateMouseStatesFromInput(inputState, deltaTime); -} - -GlobeBrowsingInteractionMode::GlobeBrowsingInteractionMode(std::shared_ptr mouseStates) - : OrbitalInteractionMode(mouseStates) -{ - -} - -GlobeBrowsingInteractionMode::~GlobeBrowsingInteractionMode() { - -} - -void GlobeBrowsingInteractionMode::setFocusNode(SceneGraphNode* focusNode) { - InteractionMode::setFocusNode(focusNode); - -#ifdef OPENSPACE_MODULE_GLOBEBROWSING_ENABLED - Renderable* baseRenderable = _focusNode->renderable(); - if (globebrowsing::RenderableGlobe* globe = dynamic_cast(baseRenderable)) { - _globe = globe; - } - else { - LWARNING("Focus node is not a renderable globe. GlobeBrowsingInteraction is not possible"); - _globe = nullptr; - } -#endif // OPENSPACE_MODULE_GLOBEBROWSING_ENABLED -} - -void GlobeBrowsingInteractionMode::updateCameraStateFromMouseStates(Camera& camera, double deltaTime) { - -#ifdef OPENSPACE_MODULE_GLOBEBROWSING_ENABLED - using namespace glm; - if (_focusNode && _globe) { - // Declare variables to use in interaction calculations - // Shrink interaction ellipsoid to enable interaction below height = 0 - double ellipsoidShrinkTerm = _globe->interactionDepthBelowEllipsoid(); - double minHeightAboveGround = _globe->generalProperties().cameraMinHeight; - - // Read the current state of the camera and focusnode - dvec3 camPos = camera.positionVec3(); - dvec3 centerPos = _focusNode->worldPosition(); - - // Follow focus nodes movement - dvec3 focusNodeDiff = centerPos - _previousFocusNodePosition; - _previousFocusNodePosition = centerPos; - camPos += focusNodeDiff; - - dquat totalRotation = camera.rotationQuaternion(); - dvec3 lookUp = camera.lookUpVectorWorldSpace(); - dvec3 camDirection = camera.viewDirectionWorldSpace(); - - // Sampling of height is done in the reference frame of the globe. - // Hence, the camera position needs to be transformed with the inverse model matrix - glm::dmat4 inverseModelTransform = _globe->inverseModelTransform(); - glm::dmat4 modelTransform = _globe->modelTransform(); - glm::dvec3 cameraPositionModelSpace = - glm::dvec3(inverseModelTransform * glm::dvec4(camPos, 1)); - - dvec3 posDiff = camPos - centerPos; - - dvec3 directionFromSurfaceToCameraModelSpace = - _globe->ellipsoid().geodeticSurfaceNormal( - _globe->ellipsoid().cartesianToGeodetic2(cameraPositionModelSpace)); - dvec3 directionFromSurfaceToCamera = - normalize(dmat3(modelTransform) * directionFromSurfaceToCameraModelSpace); - dvec3 centerToEllipsoidSurface = dmat3(modelTransform) * (_globe->projectOnEllipsoid(cameraPositionModelSpace) - - directionFromSurfaceToCameraModelSpace * ellipsoidShrinkTerm); - dvec3 ellipsoidSurfaceToCamera = camPos - (centerPos + centerToEllipsoidSurface); - - double heightToSurface = - _globe->getHeight(cameraPositionModelSpace) + ellipsoidShrinkTerm; - - double distFromCenterToSurface = - length(centerToEllipsoidSurface); - double distFromEllipsoidSurfaceToCamera = length(ellipsoidSurfaceToCamera); - double distFromCenterToCamera = length(posDiff); - double distFromSurfaceToCamera = - distFromEllipsoidSurfaceToCamera - heightToSurface; - - // Create the internal representation of the local and global camera rotations - dmat4 lookAtMat = lookAt( - dvec3(0.0, 0.0, 0.0), - -directionFromSurfaceToCamera, - normalize(camDirection + lookUp)); // To avoid problem with lookup in up direction - dquat globalCameraRotation = normalize(quat_cast(inverse(lookAtMat))); - dquat localCameraRotation = inverse(globalCameraRotation) * totalRotation; - - // Rotate with the globe - dmat3 globeStateMatrix = _focusNode->worldRotationMatrix(); - dquat globeRotation = quat_cast(globeStateMatrix); - dquat focusNodeRotationDiff = _previousFocusNodeRotation * inverse(globeRotation); - _previousFocusNodeRotation = globeRotation; - - { // Do local roll - glm::dquat cameraRollRotation = - glm::angleAxis(_mouseStates->synchedLocalRollMouseVelocity().x, dvec3(0, 0, 1)); - localCameraRotation = localCameraRotation * cameraRollRotation; - } - if(!_rotateToFocusNodeInterpolator.isInterpolating()) - { // Do local rotation - glm::dvec3 eulerAngles(_mouseStates->synchedLocalRotationMouseVelocity().y, _mouseStates->synchedLocalRotationMouseVelocity().x, 0); - glm::dquat rotationDiff = glm::dquat(eulerAngles); - - localCameraRotation = localCameraRotation * rotationDiff; - } - else - { // Interpolate local rotation to focus node - double t = _rotateToFocusNodeInterpolator.value(); - localCameraRotation = slerp(localCameraRotation, dquat(dvec3(0.0)), t); - _rotateToFocusNodeInterpolator.step(0.002); // Should probably depend on dt - // This is a fast but ugly solution to slow regaining of control... - if (t > 0.2) { - _rotateToFocusNodeInterpolator.end(); - } - } - { // Do global rotation (horizontal movement) - glm::dvec3 eulerAngles = glm::dvec3( - -_mouseStates->synchedGlobalRotationMouseVelocity().y, - -_mouseStates->synchedGlobalRotationMouseVelocity().x, - 0) * glm::clamp(distFromSurfaceToCamera / distFromCenterToSurface, 0.0, 1.0); - glm::dquat rotationDiffCamSpace = glm::dquat(eulerAngles); - - glm::dquat rotationDiffWorldSpace = - globalCameraRotation * - rotationDiffCamSpace * - glm::inverse(globalCameraRotation); - - glm::dvec3 rotationDiffVec3 = - (distFromCenterToCamera * directionFromSurfaceToCamera) - * rotationDiffWorldSpace - - (distFromCenterToCamera * directionFromSurfaceToCamera); - - camPos += rotationDiffVec3; - - posDiff = camPos - centerPos; - glm::dvec3 rotationDiffVec3AroundCenter = - posDiff - * focusNodeRotationDiff - - (posDiff); - camPos += rotationDiffVec3AroundCenter; - - - - - - cameraPositionModelSpace = - glm::dvec3(inverseModelTransform * glm::dvec4(camPos, 1)); - - directionFromSurfaceToCameraModelSpace = - _globe->ellipsoid().geodeticSurfaceNormal( - _globe->ellipsoid().cartesianToGeodetic2(cameraPositionModelSpace)); - directionFromSurfaceToCamera = - normalize(dmat3(modelTransform) * directionFromSurfaceToCameraModelSpace); - centerToEllipsoidSurface = dmat3(modelTransform) * (_globe->projectOnEllipsoid(cameraPositionModelSpace) - - directionFromSurfaceToCameraModelSpace * ellipsoidShrinkTerm); - ellipsoidSurfaceToCamera = camPos - (centerPos + centerToEllipsoidSurface); - - - glm::dvec3 lookUpWhenFacingSurface = - inverse(focusNodeRotationDiff) * globalCameraRotation * glm::dvec3(camera.lookUpVectorCameraSpace()); - glm::dmat4 lookAtMat = glm::lookAt( - glm::dvec3(0, 0, 0), - -directionFromSurfaceToCamera, - lookUpWhenFacingSurface); - globalCameraRotation = - glm::normalize(glm::quat_cast(glm::inverse(lookAtMat))); - } - { // Move position towards or away from focus node - camPos += -directionFromSurfaceToCamera * distFromSurfaceToCamera * - _mouseStates->synchedTruckMovementMouseVelocity().y; - } - { // Roll around ellipsoid normal - glm::dquat cameraRollRotation = - glm::angleAxis(_mouseStates->synchedGlobalRollMouseVelocity().x, directionFromSurfaceToCamera); - globalCameraRotation = cameraRollRotation * globalCameraRotation; - } - { // Push up to surface - ellipsoidSurfaceToCamera = camPos - (centerPos + centerToEllipsoidSurface); - - // Sampling of height is done in the reference frame of the globe. - // Hence, the camera position needs to be transformed with the inverse model matrix - glm::dmat4 inverseModelTransform = _globe->inverseModelTransform(); - glm::dvec3 cameraPositionModelSpace = - glm::dvec3(inverseModelTransform * glm::dvec4(camPos, 1)); - - distFromEllipsoidSurfaceToCamera = glm::length(ellipsoidSurfaceToCamera); - double heightToSurface = - _globe->getHeight(cameraPositionModelSpace) + ellipsoidShrinkTerm; - double heightToSurfaceAndPadding = heightToSurface + minHeightAboveGround; - camPos += directionFromSurfaceToCamera * - glm::max(heightToSurfaceAndPadding - distFromEllipsoidSurfaceToCamera, 0.0); - } - - // Update the camera state - camera.setPositionVec3(camPos); - camera.setRotation(globalCameraRotation * localCameraRotation); - } -#endif // OPENSPACE_MODULE_GLOBEBROWSING_ENABLED -} - -bool GlobeBrowsingInteractionMode::followingNodeRotation() const { - return true; -} - -#ifdef OPENSPACE_MODULE_GLOBEBROWSING_ENABLED -void GlobeBrowsingInteractionMode::goToChunk(Camera& camera, - globebrowsing::TileIndex ti, glm::vec2 uv, bool resetCameraDirection) { - using namespace globebrowsing; - - // Camera position in model space - glm::dvec3 camPos = camera.positionVec3(); - glm::dmat4 inverseModelTransform = _globe->inverseModelTransform(); - glm::dvec3 cameraPositionModelSpace = - glm::dvec3(inverseModelTransform * glm::dvec4(camPos, 1)); - - GeodeticPatch patch(ti); - Geodetic2 corner = patch.getCorner(SOUTH_WEST); - Geodetic2 positionOnPatch = patch.getSize(); - positionOnPatch.lat *= uv.y; - positionOnPatch.lon *= uv.x; - Geodetic2 pointPosition = corner + positionOnPatch; - - glm::dvec3 positionOnEllipsoid = - _globe->ellipsoid().geodeticSurfaceProjection(cameraPositionModelSpace); - double altitude = glm::length(cameraPositionModelSpace - positionOnEllipsoid); - - goToGeodetic3(camera, {pointPosition, altitude}); - - if (resetCameraDirection) { - this->resetCameraDirection(camera, pointPosition); - } -} - -void GlobeBrowsingInteractionMode::goToGeodetic2(Camera& camera, - globebrowsing::Geodetic2 geo2, bool resetCameraDirection) { - using namespace globebrowsing; - - // Camera position in model space - glm::dvec3 camPos = camera.positionVec3(); - glm::dmat4 inverseModelTransform = _globe->inverseModelTransform(); - glm::dvec3 cameraPositionModelSpace = - glm::dvec3(inverseModelTransform * glm::dvec4(camPos, 1)); - - glm::dvec3 positionOnEllipsoid = - _globe->ellipsoid().geodeticSurfaceProjection(cameraPositionModelSpace); - double altitude = glm::length(cameraPositionModelSpace - positionOnEllipsoid); - - goToGeodetic3(camera, {geo2, altitude}); - - if (resetCameraDirection) { - this->resetCameraDirection(camera, geo2); - } -} - -void GlobeBrowsingInteractionMode::goToGeodetic3(Camera& camera, globebrowsing::Geodetic3 geo3) { - glm::dvec3 positionModelSpace = _globe->ellipsoid().cartesianPosition(geo3); - glm::dmat4 modelTransform = _globe->modelTransform(); - glm::dvec3 positionWorldSpace = modelTransform * glm::dvec4(positionModelSpace, 1.0); - camera.setPositionVec3(positionWorldSpace); -} - - void GlobeBrowsingInteractionMode::resetCameraDirection(Camera& camera, globebrowsing::Geodetic2 geo2) { - using namespace globebrowsing; - - // Camera is described in world space - glm::dmat4 modelTransform = _globe->modelTransform(); - - // Lookup vector - glm::dvec3 positionModelSpace = _globe->ellipsoid().cartesianSurfacePosition(geo2); - glm::dvec3 slightlyNorth = _globe->ellipsoid().cartesianSurfacePosition( - Geodetic2(geo2.lat + 0.001, geo2.lon)); - glm::dvec3 lookUpModelSpace = glm::normalize(slightlyNorth - positionModelSpace); - glm::dvec3 lookUpWorldSpace = glm::dmat3(modelTransform) * lookUpModelSpace; - - // Lookat vector - glm::dvec3 lookAtWorldSpace = modelTransform * glm::dvec4(positionModelSpace, 1.0); - - // Eye position - glm::dvec3 eye = camera.positionVec3(); - - // Matrix - glm::dmat4 lookAtMatrix = glm::lookAt( - eye, lookAtWorldSpace, lookUpWorldSpace); - - // Set rotation - glm::dquat rotation = glm::quat_cast(inverse(lookAtMatrix)); - camera.setRotation(rotation); - } - -#endif // OPENSPACE_MODULE_GLOBEBROWSING_ENABLED - -} // namespace interaction -} // namespace openspace diff --git a/src/interaction/keybindingmanager.cpp b/src/interaction/keybindingmanager.cpp new file mode 100644 index 0000000000..2f3db8e2bc --- /dev/null +++ b/src/interaction/keybindingmanager.cpp @@ -0,0 +1,178 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2017 * + * * + * 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 + +namespace { + const char* MainTemplateFilename = "${OPENSPACE_DATA}/web/keybindings/main.hbs"; + const char* KeybindingTemplateFilename = "${OPENSPACE_DATA}/web/keybindings/keybinding.hbs"; + const char* JsFilename = "${OPENSPACE_DATA}/web/keybindings/script.js"; +} // namespace + +#include "keybindingmanager_lua.inl" + +namespace openspace { +namespace interaction { + +KeyBindingManager::KeyBindingManager() + : DocumentationGenerator( + "Documentation", + "keybindings", + { + { "keybindingTemplate", KeybindingTemplateFilename }, + { "mainTemplate", MainTemplateFilename } + }, + JsFilename + ) +{ } + +void KeyBindingManager::keyboardCallback(Key key, KeyModifier modifier, KeyAction action) { + if (action == KeyAction::Press || action == KeyAction::Repeat) { + // iterate over key bindings + std::pair::iterator, + std::multimap::iterator> ret = + _keyLua.equal_range({ key, modifier }); + for (std::multimap::iterator it = ret.first; + it != ret.second; + ++it) + { + scripting::ScriptEngine::RemoteScripting remote = + it->second.synchronization ? + scripting::ScriptEngine::RemoteScripting::Yes : + scripting::ScriptEngine::RemoteScripting::No; + + OsEng.scriptEngine().queueScript(it->second.command, remote); + } + } +} + +void KeyBindingManager::resetKeyBindings() { + _keyLua.clear(); +} + +void KeyBindingManager::bindKeyLocal(Key key, KeyModifier modifier, + std::string luaCommand, std::string documentation) +{ + _keyLua.insert({ + { key, modifier }, + { + std::move(luaCommand), + Synchronized::No, + std::move(documentation) + } + }); +} + +void KeyBindingManager::bindKey(Key key, KeyModifier modifier, + std::string luaCommand, std::string documentation) +{ + _keyLua.insert({ + { key, modifier }, + { + std::move(luaCommand), + Synchronized::Yes, + std::move(documentation) + } + }); +} + +std::string KeyBindingManager::generateJson() const { + std::stringstream json; + json << "["; + bool first = true; + for (const std::pair& p : _keyLua) { + if (!first) { + json << ","; + } + first = false; + json << "{"; + json << "\"key\": \"" << std::to_string(p.first) << "\","; + json << "\"script\": \"" << p.second.command << "\","; + json << "\"remoteScripting\": " << (p.second.synchronization ? "true," : "false,"); + json << "\"documentation\": \"" << p.second.documentation << "\""; + json << "}"; + } + json << "]"; + + std::string jsonString = ""; + for (const char& c : json.str()) { + if (c == '\'') { + jsonString += "\\'"; + } else { + jsonString += c; + } + } + + return jsonString; +} + +scripting::LuaLibrary KeyBindingManager::luaLibrary() { + return { + "", + { + { + "clearKeys", + &luascriptfunctions::clearKeys, + "", + "Clear all key bindings" + }, + { + "bindKey", + &luascriptfunctions::bindKey, + "string, string [,string]", + "Binds a key by name to a lua string command to execute both locally " + "and to broadcast to clients if this is the host of a parallel session. " + "The first argument is the key, the second argument is the Lua command " + "that is to be executed, and the optional third argument is a human " + "readable description of the command for documentation purposes." + }, + { + "bindKeyLocal", + &luascriptfunctions::bindKeyLocal, + "string, string [,string]", + "Binds a key by name to a lua string command to execute only locally. " + "The first argument is the key, the second argument is the Lua command " + "that is to be executed, and the optional third argument is a human " + "readable description of the command for documentation purposes." + }, + } + }; +} + +} // namespace interaction +} // namespace openspace diff --git a/src/interaction/interactionhandler_lua.inl b/src/interaction/keybindingmanager_lua.inl similarity index 61% rename from src/interaction/interactionhandler_lua.inl rename to src/interaction/keybindingmanager_lua.inl index 34b2db6312..ba6b75e423 100644 --- a/src/interaction/interactionhandler_lua.inl +++ b/src/interaction/keybindingmanager_lua.inl @@ -25,38 +25,6 @@ namespace openspace { namespace luascriptfunctions { -/** - * \ingroup LuaScripts - * setOrigin(): - * Set the origin of the camera - */ -int setOrigin(lua_State* L) { - using ghoul::lua::luaTypeToString; - - int nArguments = lua_gettop(L); - if (nArguments != 1) - return luaL_error(L, "Expected %i arguments, got %i", 1, nArguments); - - const int type = lua_type(L, -1); - if (type != LUA_TSTRING) - return luaL_error(L, "Expected string, got %i", type); - - std::string s = luaL_checkstring(L, -1); - - SceneGraphNode* node = sceneGraphNode(s); - if (!node) { - LWARNINGC( - "lua.setOrigin", - "Could not find a node in scenegraph called '" << s << "'" - ); - return 0; - } - - OsEng.interactionHandler().setFocusNode(node); - - return 0; -} - /** * \ingroup LuaScripts * bindKey(): @@ -95,7 +63,7 @@ int bindKey(lua_State* L) { documentation = luaL_checkstring(L, DocumentationLocation); } - OsEng.interactionHandler().bindKey( + OsEng.keyBindingManager().bindKey( iKey.key, iKey.modifier, std::move(command), @@ -141,7 +109,7 @@ int bindKeyLocal(lua_State* L) { documentation = luaL_checkstring(L, DocumentationLocation); } - OsEng.interactionHandler().bindKeyLocal( + OsEng.keyBindingManager().bindKeyLocal( iKey.key, iKey.modifier, std::move(command), @@ -151,7 +119,6 @@ int bindKeyLocal(lua_State* L) { return 0; } - /** * \ingroup LuaScripts * clearKeys(): @@ -164,89 +131,11 @@ int clearKeys(lua_State* L) { if (nArguments != 0) return luaL_error(L, "Expected %i arguments, got %i", 0, nArguments); - OsEng.interactionHandler().resetKeyBindings(); + OsEng.keyBindingManager().resetKeyBindings(); return 0; } -int goToChunk(lua_State* L) { - using ghoul::lua::luaTypeToString; - - int nArguments = lua_gettop(L); - if (nArguments != 3) - return luaL_error(L, "Expected %i arguments, got %i", 3, nArguments); - - int x = static_cast(lua_tonumber(L, 1)); - int y = static_cast(lua_tonumber(L, 2)); - int level = static_cast(lua_tonumber(L, 3)); - - OsEng.interactionHandler().goToChunk(x, y, level); - - return 0; -} - -int goToGeo(lua_State* L) { - using ghoul::lua::luaTypeToString; - - int nArguments = lua_gettop(L); - if (nArguments != 2) - return luaL_error(L, "Expected %i arguments, got %i", 2, nArguments); - - double latitude = static_cast(lua_tonumber(L, 1)); - double longitude = static_cast(lua_tonumber(L, 2)); - - OsEng.interactionHandler().goToGeo(latitude, longitude); - - return 0; -} - -int restoreCameraStateFromFile(lua_State* L) { - using ghoul::lua::luaTypeToString; - - int nArguments = lua_gettop(L); - if (nArguments != 1) - return luaL_error(L, "Expected %i arguments, got %i", 1, nArguments); - - std::string cameraStateFilePath = luaL_checkstring(L, -1); - - if (cameraStateFilePath.empty()) { - return luaL_error(L, "filepath string is empty"); - } - - OsEng.interactionHandler().restoreCameraStateFromFile(cameraStateFilePath); - return 0; -} - -int saveCameraStateToFile(lua_State* L) { - using ghoul::lua::luaTypeToString; - - int nArguments = lua_gettop(L); - if (nArguments != 1) - return luaL_error(L, "Expected %i arguments, got %i", 1, nArguments); - - std::string cameraStateFilePath = luaL_checkstring(L, -1); - - if (cameraStateFilePath.empty()) { - return luaL_error(L, "filepath string is empty"); - } - - OsEng.interactionHandler().saveCameraStateToFile(cameraStateFilePath); - return 0; -} - -int resetCameraDirection(lua_State* L) { - using ghoul::lua::luaTypeToString; - - int nArguments = lua_gettop(L); - if (nArguments != 0) { - return luaL_error(L, "Expected %i arguments, got %i", 0, nArguments); - } - - OsEng.interactionHandler().resetCameraDirection(); - return 0; -} - - } // namespace luascriptfunctions } // namespace openspace diff --git a/src/interaction/keyframenavigator.cpp b/src/interaction/keyframenavigator.cpp new file mode 100644 index 0000000000..1ce1773e2f --- /dev/null +++ b/src/interaction/keyframenavigator.cpp @@ -0,0 +1,127 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2017 * + * * + * 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 + +namespace openspace { +namespace interaction { + +void KeyframeNavigator::updateCamera(Camera& camera) { + double now = OsEng.runTime(); + + if (_cameraPoseTimeline.nKeyframes() == 0) { + return; + } + + const Keyframe* nextKeyframe = _cameraPoseTimeline.firstKeyframeAfter(now); + const Keyframe* prevKeyframe = _cameraPoseTimeline.lastKeyframeBefore(now); + double nextTime = 0; + double prevTime = 0; + double t = 0; + + if (nextKeyframe) { + nextTime = nextKeyframe->timestamp; + } else { + return; + } + + if (prevKeyframe) { + prevTime = prevKeyframe->timestamp; + t = (now - prevTime) / (nextTime - prevTime); + } else { + // If there is no keyframe before: Only use the next keyframe. + prevTime = nextTime; + prevKeyframe = nextKeyframe; + t = 1; + } + + _cameraPoseTimeline.removeKeyframesBefore(prevTime); + + const CameraPose& prevPose = prevKeyframe->data; + const CameraPose& nextPose = nextKeyframe->data; + + Scene* scene = camera.parent()->scene(); + SceneGraphNode* prevFocusNode = scene->sceneGraphNode(prevPose.focusNode); + SceneGraphNode* nextFocusNode = scene->sceneGraphNode(nextPose.focusNode); + + if (!prevFocusNode || !nextFocusNode) { + return; + } + + glm::dvec3 prevKeyframeCameraPosition = prevPose.position; + glm::dvec3 nextKeyframeCameraPosition = nextPose.position; + glm::dquat prevKeyframeCameraRotation = prevPose.rotation; + glm::dquat nextKeyframeCameraRotation = nextPose.rotation; + + // Transform position and rotation based on focus node rotation (if following rotation) + if (prevPose.followFocusNodeRotation) { + prevKeyframeCameraRotation = prevFocusNode->worldRotationMatrix() * glm::dmat3(glm::dquat(prevPose.rotation)); + prevKeyframeCameraPosition = prevFocusNode->worldRotationMatrix() * prevPose.position; + } + if (nextPose.followFocusNodeRotation) { + nextKeyframeCameraRotation = nextFocusNode->worldRotationMatrix() * glm::dmat3(glm::dquat(nextPose.rotation)); + nextKeyframeCameraPosition = nextFocusNode->worldRotationMatrix() * nextPose.position; + } + + // Transform position based on focus node position + prevKeyframeCameraPosition += prevFocusNode->worldPosition(); + nextKeyframeCameraPosition += nextFocusNode->worldPosition(); + + // Linear interpolation + camera.setPositionVec3(prevKeyframeCameraPosition * (1 - t) + nextKeyframeCameraPosition * t); + camera.setRotation(glm::slerp(prevKeyframeCameraRotation, nextKeyframeCameraRotation, t)); +} + +Timeline& KeyframeNavigator::timeline() { + return _cameraPoseTimeline; +} + +void KeyframeNavigator::addKeyframe(double timestamp, KeyframeNavigator::CameraPose pose) { + timeline().addKeyframe(timestamp, pose); +} + +void KeyframeNavigator::removeKeyframesAfter(double timestamp) { + timeline().removeKeyframesAfter(timestamp); +} + +void KeyframeNavigator::clearKeyframes() { + timeline().clearKeyframes(); +} + +size_t KeyframeNavigator::nKeyframes() const { + return _cameraPoseTimeline.nKeyframes(); +} + +} // namespace interaction +} // namespace openspace diff --git a/src/interaction/mousestate.cpp b/src/interaction/mousestate.cpp new file mode 100644 index 0000000000..f6e61e9371 --- /dev/null +++ b/src/interaction/mousestate.cpp @@ -0,0 +1,177 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2017 * + * * + * 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 + +namespace openspace { +namespace interaction { + +MouseState::MouseState(double scaleFactor) + : velocity(scaleFactor, 1) + , previousPosition(0.0, 0.0) +{ } + +void MouseState::setFriction(double friction) { + velocity.setFriction(friction); +} + +void MouseState::setVelocityScaleFactor(double scaleFactor) { + velocity.setScaleFactor(scaleFactor); +} + +MouseStates::MouseStates(double sensitivity, double velocityScaleFactor) + : _sensitivity(sensitivity) + , _globalRotationMouseState(velocityScaleFactor) + , _localRotationMouseState(velocityScaleFactor) + , _truckMovementMouseState(velocityScaleFactor) + , _localRollMouseState(velocityScaleFactor) + , _globalRollMouseState(velocityScaleFactor) +{ } + +void MouseStates::updateMouseStatesFromInput(const InputState& inputState, + double deltaTime) +{ + glm::dvec2 mousePosition = inputState.getMousePosition(); + + bool button1Pressed = inputState.isMouseButtonPressed(MouseButton::Button1); + bool button2Pressed = inputState.isMouseButtonPressed(MouseButton::Button2); + bool button3Pressed = inputState.isMouseButtonPressed(MouseButton::Button3); + bool keyCtrlPressed = inputState.isKeyPressed(Key::LeftControl); + bool keyShiftPressed = inputState.isKeyPressed(Key::LeftShift); + + // Update the mouse states + if (button1Pressed && !keyShiftPressed) { + if (keyCtrlPressed) { + glm::dvec2 mousePositionDelta = + _localRotationMouseState.previousPosition - mousePosition; + _localRotationMouseState.velocity.set( + mousePositionDelta * _sensitivity, deltaTime); + + _globalRotationMouseState.previousPosition = mousePosition; + _globalRotationMouseState.velocity.decelerate(deltaTime); + } + else { + glm::dvec2 mousePositionDelta = + _globalRotationMouseState.previousPosition - mousePosition; + _globalRotationMouseState.velocity.set( + mousePositionDelta * _sensitivity, deltaTime); + + _localRotationMouseState.previousPosition = mousePosition; + _localRotationMouseState.velocity.decelerate(deltaTime); + } + } + else { // !button1Pressed + _localRotationMouseState.previousPosition = mousePosition; + _localRotationMouseState.velocity.decelerate(deltaTime); + + _globalRotationMouseState.previousPosition = mousePosition; + _globalRotationMouseState.velocity.decelerate(deltaTime); + } + if (button2Pressed) { + glm::dvec2 mousePositionDelta = + _truckMovementMouseState.previousPosition - mousePosition; + _truckMovementMouseState.velocity.set( + mousePositionDelta * _sensitivity, deltaTime); + } + else { // !button2Pressed + _truckMovementMouseState.previousPosition = mousePosition; + _truckMovementMouseState.velocity.decelerate(deltaTime); + } + if (button3Pressed || (keyShiftPressed && button1Pressed)) { + if (keyCtrlPressed) { + glm::dvec2 mousePositionDelta = + _localRollMouseState.previousPosition - mousePosition; + _localRollMouseState.velocity.set( + mousePositionDelta * _sensitivity, deltaTime); + + _globalRollMouseState.previousPosition = mousePosition; + _globalRollMouseState.velocity.decelerate(deltaTime); + } + else { + glm::dvec2 mousePositionDelta = + _globalRollMouseState.previousPosition - mousePosition; + _globalRollMouseState.velocity.set( + mousePositionDelta * _sensitivity, deltaTime); + + _localRollMouseState.previousPosition = mousePosition; + _localRollMouseState.velocity.decelerate(deltaTime); + } + } + else { // !button3Pressed + _globalRollMouseState.previousPosition = mousePosition; + _globalRollMouseState.velocity.decelerate(deltaTime); + + _localRollMouseState.previousPosition = mousePosition; + _localRollMouseState.velocity.decelerate(deltaTime); + } +} + +void MouseStates::setRotationalFriction(double friction) { + _localRotationMouseState.setFriction(friction); + _localRollMouseState.setFriction(friction); + _globalRollMouseState.setFriction(friction); +} + +void MouseStates::setHorizontalFriction(double friction) { + _globalRotationMouseState.setFriction(friction); +} + +void MouseStates::setVerticalFriction(double friction) { + _truckMovementMouseState.setFriction(friction); +} + +void MouseStates::setSensitivity(double sensitivity) { + _sensitivity = sensitivity; +} + +void MouseStates::setVelocityScaleFactor(double scaleFactor) { + _globalRotationMouseState.setVelocityScaleFactor(scaleFactor); + _localRotationMouseState.setVelocityScaleFactor(scaleFactor); + _truckMovementMouseState.setVelocityScaleFactor(scaleFactor); + _localRollMouseState.setVelocityScaleFactor(scaleFactor); + _globalRollMouseState.setVelocityScaleFactor(scaleFactor); +} + +glm::dvec2 MouseStates::globalRotationMouseVelocity() const{ + return _globalRotationMouseState.velocity.get(); +} + +glm::dvec2 MouseStates::localRotationMouseVelocity() const{ + return _localRotationMouseState.velocity.get(); +} + +glm::dvec2 MouseStates::truckMovementMouseVelocity() const{ + return _truckMovementMouseState.velocity.get(); +} + +glm::dvec2 MouseStates::localRollMouseVelocity() const{ + return _localRollMouseState.velocity.get(); +} + +glm::dvec2 MouseStates::globalRollMouseVelocity() const{ + return _globalRollMouseState.velocity.get(); +} + +} // namespace interaction +} // namespace openspace diff --git a/src/interaction/navigationhandler.cpp b/src/interaction/navigationhandler.cpp new file mode 100644 index 0000000000..43218accc1 --- /dev/null +++ b/src/interaction/navigationhandler.cpp @@ -0,0 +1,299 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2017 * + * * + * 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 + +namespace { + const char* _loggerCat = "NavigationHandler"; + + const char* KeyFocus = "Focus"; + const char* KeyPosition = "Position"; + const char* KeyRotation = "Rotation"; +} // namespace + +#include "navigationhandler_lua.inl" + +namespace openspace { +namespace interaction { + +NavigationHandler::NavigationHandler() + : properties::PropertyOwner("NavigationHandler") + , _origin("origin", "Origin", "") + , _useKeyFrameInteraction("useKeyFrameInteraction", "Use keyframe interaction", false) +{ + _origin.onChange([this]() { + SceneGraphNode* node = sceneGraphNode(_origin.value()); + if (!node) { + LWARNING("Could not find a node in scenegraph called '" << _origin.value() << "'"); + return; + } + setFocusNode(node); + resetCameraDirection(); + }); + + _inputState = std::make_unique(); + _orbitalNavigator = std::make_unique(); + _keyframeNavigator = std::make_unique(); + + // Add the properties + addProperty(_origin); + addProperty(_useKeyFrameInteraction); + addPropertySubOwner(*_orbitalNavigator); +} + +NavigationHandler::~NavigationHandler() +{ } + +void NavigationHandler::initialize() { + OsEng.parallelConnection().connectionEvent()->subscribe("NavigationHandler", "statusChanged", [this]() { + if (OsEng.parallelConnection().status() == ParallelConnection::Status::ClientWithHost) { + _useKeyFrameInteraction = true; + } + }); +} + +void NavigationHandler::deinitialize() { + OsEng.parallelConnection().connectionEvent()->unsubscribe("NavigationHandler"); +} + +void NavigationHandler::setFocusNode(SceneGraphNode* node) { + _orbitalNavigator->setFocusNode(node); + _camera->setFocusPositionVec3(focusNode()->worldPosition()); +} + +void NavigationHandler::setCamera(Camera* camera) { + _camera = camera; + //setFocusNode(_camera->parent()); +} + +void NavigationHandler::resetCameraDirection() { + LINFO("Setting camera direction to point at focus node."); + _orbitalNavigator->startInterpolateCameraDirection(*_camera); +} + +const OrbitalNavigator& NavigationHandler::orbitalNavigator() const { + return *_orbitalNavigator; +} + +KeyframeNavigator& NavigationHandler::keyframeNavigator() const { + return *_keyframeNavigator; +} + +void NavigationHandler::updateCamera(double deltaTime) { + ghoul_assert(_inputState != nullptr, "InputState cannot be null!"); + ghoul_assert(_camera != nullptr, "Camera cannot be null!"); + + if (_cameraUpdatedFromScript) { + _cameraUpdatedFromScript = false; + } + else { + if (_camera && focusNode()) { + if (_useKeyFrameInteraction) { + _keyframeNavigator->updateCamera(*_camera); + } + else { + _orbitalNavigator->updateMouseStatesFromInput(*_inputState, deltaTime); + _orbitalNavigator->updateCameraStateFromMouseStates(*_camera, deltaTime); + } + _camera->setFocusPositionVec3(focusNode()->worldPosition()); + } + } +} + +SceneGraphNode* NavigationHandler::focusNode() const { + return _orbitalNavigator->focusNode(); +} + +glm::dvec3 NavigationHandler::focusNodeToCameraVector() const { + return _camera->positionVec3() - focusNode()->worldPosition(); +} + +glm::quat NavigationHandler::focusNodeToCameraRotation() const { + glm::dmat4 invWorldRotation = glm::inverse(focusNode()->worldRotationMatrix()); + return glm::quat(invWorldRotation) * glm::quat(_camera->rotationQuaternion()); +} + +Camera* NavigationHandler::camera() const { + return _camera; +} + +const InputState& NavigationHandler::inputState() const { + return *_inputState; +} + +void NavigationHandler::mouseButtonCallback(MouseButton button, MouseAction action) { + _inputState->mouseButtonCallback(button, action); +} + +void NavigationHandler::mousePositionCallback(double x, double y) { + _inputState->mousePositionCallback(x, y); +} + +void NavigationHandler::mouseScrollWheelCallback(double pos) { + _inputState->mouseScrollWheelCallback(pos); +} + +void NavigationHandler::keyboardCallback(Key key, KeyModifier modifier, KeyAction action) { + _inputState->keyboardCallback(key, modifier, action); +} + +void NavigationHandler::setCameraStateFromDictionary(const ghoul::Dictionary& cameraDict) { + bool readSuccessful = true; + + std::string focus; + glm::dvec3 cameraPosition; + glm::dvec4 cameraRotation; // Need to read the quaternion as a vector first. + + readSuccessful &= cameraDict.getValue(KeyFocus, focus); + readSuccessful &= cameraDict.getValue(KeyPosition, cameraPosition); + readSuccessful &= cameraDict.getValue(KeyRotation, cameraRotation); + + if (!readSuccessful) { + throw ghoul::RuntimeError( + "Position, Rotation and Focus need to be defined for camera dictionary."); + } + + SceneGraphNode* node = sceneGraphNode(focus); + if (!node) { + throw ghoul::RuntimeError( + "Could not find a node in scenegraph called '" + focus + "'"); + } + + // Set state + setFocusNode(node); + _camera->setPositionVec3(cameraPosition); + _camera->setRotation(glm::dquat( + cameraRotation.x, cameraRotation.y, cameraRotation.z, cameraRotation.w)); +} + +ghoul::Dictionary NavigationHandler::getCameraStateDictionary() { + glm::dvec3 cameraPosition; + glm::dquat quat; + glm::dvec4 cameraRotation; + + cameraPosition = _camera->positionVec3(); + quat = _camera->rotationQuaternion(); + cameraRotation = glm::dvec4(quat.w, quat.x, quat.y, quat.z); + + ghoul::Dictionary cameraDict; + cameraDict.setValue(KeyPosition, cameraPosition); + cameraDict.setValue(KeyRotation, cameraRotation); + cameraDict.setValue(KeyFocus, focusNode()->name()); + + return cameraDict; +} + +void NavigationHandler::saveCameraStateToFile(const std::string& filepath) { + if (!filepath.empty()) { + std::string fullpath = absPath(filepath); + LINFO("Saving camera position: " << filepath); + + ghoul::Dictionary cameraDict = getCameraStateDictionary(); + + // TODO : Should get the camera state as a dictionary and save the dictionary to + // a file in form of a lua state and not use ofstreams here. + + std::ofstream ofs(fullpath.c_str()); + + glm::dvec3 p = _camera->positionVec3(); + glm::dquat q = _camera->rotationQuaternion(); + + ofs << "return {" << std::endl; + ofs << " " << KeyFocus << " = " << "\"" << focusNode()->name() << "\"" << "," << std::endl; + ofs << " " << KeyPosition << " = {" + << std::to_string(p.x) << ", " + << std::to_string(p.y) << ", " + << std::to_string(p.z) << "}," << std::endl; + ofs << " " << KeyRotation << " = {" + << std::to_string(q.w) << ", " + << std::to_string(q.x) << ", " + << std::to_string(q.y) << ", " + << std::to_string(q.z) << "}," << std::endl; + ofs << "}"<< std::endl; + + ofs.close(); + } +} + +void NavigationHandler::restoreCameraStateFromFile(const std::string& filepath) { + LINFO("Reading camera state from file: " << filepath); + if (!FileSys.fileExists(filepath)) + throw ghoul::FileNotFoundError(filepath, "CameraFilePath"); + + ghoul::Dictionary cameraDict; + try { + ghoul::lua::loadDictionaryFromFile(filepath, cameraDict); + setCameraStateFromDictionary(cameraDict); + _cameraUpdatedFromScript = true; + } + catch (ghoul::RuntimeError& e) { + LWARNING("Unable to set camera position"); + LWARNING(e.message); + } +} + +scripting::LuaLibrary NavigationHandler::luaLibrary() { + return { + "navigation", + { + { + "saveCameraStateToFile", + &luascriptfunctions::saveCameraStateToFile, + "string", + "Save the current camera state to file" + }, + { + "restoreCameraStateFromFile", + &luascriptfunctions::restoreCameraStateFromFile, + "string", + "Restore the camera state from file" + }, + { + "resetCameraDirection", + &luascriptfunctions::resetCameraDirection, + "void", + "Reset the camera direction to point at the focus node" + } + } + }; +} + +} // namespace interaction +} // namespace openspace diff --git a/src/interaction/navigationhandler_lua.inl b/src/interaction/navigationhandler_lua.inl new file mode 100644 index 0000000000..64d9bf1a21 --- /dev/null +++ b/src/interaction/navigationhandler_lua.inl @@ -0,0 +1,113 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2017 * + * * + * 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. * + ****************************************************************************************/ + +namespace openspace { + +namespace luascriptfunctions { +/** + * \ingroup LuaScripts + * setOrigin(): + * Set the origin of the camera + */ +int setOrigin(lua_State* L) { + using ghoul::lua::luaTypeToString; + + int nArguments = lua_gettop(L); + if (nArguments != 1) { + return luaL_error(L, "Expected %i arguments, got %i", 1, nArguments); + } + + const int type = lua_type(L, -1); + if (type != LUA_TSTRING) { + return luaL_error(L, "Expected string, got %i", type); + } + + std::string s = luaL_checkstring(L, -1); + + SceneGraphNode* node = sceneGraphNode(s); + if (!node) { + LWARNINGC( + "lua.setOrigin", + "Could not find a node in scenegraph called '" << s << "'" + ); + return 0; + } + + OsEng.navigationHandler().setFocusNode(node); + + return 0; +} + +int restoreCameraStateFromFile(lua_State* L) { + using ghoul::lua::luaTypeToString; + + int nArguments = lua_gettop(L); + if (nArguments != 1) { + return luaL_error(L, "Expected %i arguments, got %i", 1, nArguments); + } + + std::string cameraStateFilePath = luaL_checkstring(L, -1); + + if (cameraStateFilePath.empty()) { + return luaL_error(L, "filepath string is empty"); + } + + OsEng.navigationHandler().restoreCameraStateFromFile(cameraStateFilePath); + return 0; +} + +int saveCameraStateToFile(lua_State* L) { + using ghoul::lua::luaTypeToString; + + int nArguments = lua_gettop(L); + if (nArguments != 1) { + return luaL_error(L, "Expected %i arguments, got %i", 1, nArguments); + } + + std::string cameraStateFilePath = luaL_checkstring(L, -1); + + if (cameraStateFilePath.empty()) { + return luaL_error(L, "filepath string is empty"); + } + + OsEng.navigationHandler().saveCameraStateToFile(cameraStateFilePath); + return 0; +} + +int resetCameraDirection(lua_State* L) { + using ghoul::lua::luaTypeToString; + + int nArguments = lua_gettop(L); + if (nArguments != 0) { + return luaL_error(L, "Expected %i arguments, got %i", 0, nArguments); + } + + OsEng.navigationHandler().resetCameraDirection(); + return 0; +} + + +} // namespace luascriptfunctions + +} // namespace openspace diff --git a/src/interaction/orbitalnavigator.cpp b/src/interaction/orbitalnavigator.cpp new file mode 100644 index 0000000000..4bbebd12c6 --- /dev/null +++ b/src/interaction/orbitalnavigator.cpp @@ -0,0 +1,532 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2017 * + * * + * 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 + +namespace { + const char* _loggerCat = "OrbitalNavigator"; +} + +namespace openspace { +namespace interaction { + +OrbitalNavigator::OrbitalNavigator() + : properties::PropertyOwner("OrbitalNavigator") + , _rotationalFriction("rotationalFriction", "Rotational friction", true) + , _horizontalFriction("horizontalFriction", "Horizontal friction", true) + , _verticalFriction("verticalFriction", "Vertical friction", true) + , _followFocusNodeRotationDistance("followFocusNodeRotationDistance", + "Follow focus node rotation distance", 2.0f, 0.0f, 10.f) + , _minimumAllowedDistance("minimumAllowedDistance", + "Minimum allowed distance", 10.0f, 0.0f, 10000.f) + , _sensitivity("sensitivity", "Sensitivity", 20.0f, 1.0f, 50.f) + , _motionLag("motionLag", "Motion lag", 0.5f, 0.f, 1.f) + , _mouseStates(_sensitivity * pow(10.0,-4), 1 / (_motionLag + 0.0000001)) +{ + auto smoothStep = + [](double t) { + double res = 3.0 * t*t - 2.0 * t*t*t; + return glm::clamp(res, 0.0, 1.0); + }; + _followRotationInterpolator.setTransferFunction(smoothStep); + + // The transfer function is used here to get a different interpolation than the one + // obtained from newValue = lerp(0, currentValue, dt). That one will result in an + // exponentially decreasing value but we want to be able to control it. Either as + // a linear interpolation or a smooth step interpolation. Therefore we use + // newValue = lerp(0, currentValue * f(t) * dt) where f(t) is the transfer function + // and lerp is a linear iterpolation + // lerp(endValue, startValue, interpolationParameter). + // + // The transfer functions are derived from: + // f(t) = d/dt ( ln(1 / f_orig(t)) ) where f_orig is the transfer function that would + // be used if the interpolation was sinply linear between a start value and an end + // value instead of current value and end value (0) as we use it when inerpoláting. + // As an example f_orig(t) = 1 - t yields f(t) = 1 / (1 - t) which results in a linear + // interpolation from 1 to 0. + + auto smoothStepDerivedTranferFunction = + [](double t) { + return (6 * (t + t*t) / (1 - 3 * t*t + 2 * t*t*t)); + }; + _rotateToFocusNodeInterpolator.setTransferFunction(smoothStepDerivedTranferFunction); + + // Define callback functions for changed properties + _rotationalFriction.onChange([&]() { + _mouseStates.setRotationalFriction(_rotationalFriction); + }); + _horizontalFriction.onChange([&]() { + _mouseStates.setHorizontalFriction(_horizontalFriction); + }); + _verticalFriction.onChange([&]() { + _mouseStates.setVerticalFriction(_verticalFriction); + }); + _sensitivity.onChange([&]() { + _mouseStates.setSensitivity(_sensitivity * pow(10.0,-4)); + }); + _motionLag.onChange([&]() { + _mouseStates.setVelocityScaleFactor(1 / (_motionLag + 0.0000001)); + }); + + // Add the properties + addProperty(_rotationalFriction); + addProperty(_horizontalFriction); + addProperty(_verticalFriction); + addProperty(_followFocusNodeRotationDistance); + addProperty(_minimumAllowedDistance); + addProperty(_sensitivity); + addProperty(_motionLag); +} + +OrbitalNavigator::~OrbitalNavigator() +{ } + +void OrbitalNavigator::updateMouseStatesFromInput(const InputState& inputState, + double deltaTime) +{ + _mouseStates.updateMouseStatesFromInput(inputState, deltaTime); +} + +void OrbitalNavigator::updateCameraStateFromMouseStates(Camera& camera, + double deltaTime) +{ + if (_focusNode) { + // Read the current state of the camera + glm::dvec3 camPos = camera.positionVec3(); + glm::dvec3 centerPos = _focusNode->worldPosition(); + + // Follow focus nodes movement + glm::dvec3 focusNodeDiff = centerPos - _previousFocusNodePosition; + _previousFocusNodePosition = centerPos; + camPos += focusNodeDiff; + + // Calculate a position handle based on the camera position in world space + SurfacePositionHandle posHandle = calculateSurfacePositionHandle(camPos); + + // Decompose camera rotation so that we can handle global and local rotation + // individually. Then we combine them again when finished. + CameraRotationDecomposition camRot = decomposeCameraRotation( + camPos, + camera.rotationQuaternion(), + camera.lookUpVectorWorldSpace(), + camera.viewDirectionWorldSpace() + ); + + // Rotate with the object by finding a differential rotation from the previous + // to the current rotation + glm::dmat3 objectStateMatrix = _focusNode->worldRotationMatrix(); + glm::dquat objectRotation = glm::quat_cast(objectStateMatrix); + glm::dquat focusNodeRotationDiff = + _previousFocusNodeRotation * glm::inverse(objectRotation); + _previousFocusNodeRotation = objectRotation; + + // Interpolate rotation differential so that the camera rotates with the object + // only if close enough + focusNodeRotationDiff = interpolateRotationDifferential( + deltaTime, + 1.0, + focusNodeRotationDiff, + centerPos, + camPos, + posHandle + ); + + // Update local rotation + camRot.localRotation = roll(deltaTime, camRot.localRotation); + camRot.localRotation = interpolateLocalRotation(deltaTime, camRot.localRotation); + camRot.localRotation = rotateLocally(deltaTime, camRot.localRotation); + + // Horizontal translation + camPos = translateHorizontally( + deltaTime, + camPos, + centerPos, + focusNodeRotationDiff, + camRot.globalRotation, + posHandle + ); + + // Horizontal translation by focus node rotation + camPos = followFocusNodeRotation( + camPos, + centerPos, + focusNodeRotationDiff + ); + + // Recalculate posHandle since horizontal position changed + posHandle = calculateSurfacePositionHandle(camPos); + + camRot.globalRotation = rotateGlobally( + camRot.globalRotation, + centerPos, + focusNodeRotationDiff, + camPos, + posHandle + ); + + // Rotate around the surface out direction + camRot.globalRotation = rotateHorizontally( + deltaTime, + camRot.globalRotation, + camPos, + posHandle + ); + + // Perform the vertical movements + camPos = translateVertically(deltaTime, camPos, centerPos, posHandle); + camPos = pushToSurface( + _minimumAllowedDistance, + camPos, + centerPos, + posHandle + ); + + // Update the camera state + camera.setPositionVec3(camPos); + camera.setRotation(camRot.globalRotation * camRot.localRotation); + } +} + +void OrbitalNavigator::setFocusNode(SceneGraphNode* focusNode) { + _focusNode = focusNode; + + if (_focusNode != nullptr) { + _previousFocusNodePosition = _focusNode->worldPosition(); + _previousFocusNodeRotation = glm::quat_cast(_focusNode->worldRotationMatrix()); + } +} + +void OrbitalNavigator::startInterpolateCameraDirection(const Camera& camera) { + glm::dvec3 camPos = camera.positionVec3(); + glm::dvec3 camDir = glm::normalize( + camera.rotationQuaternion() * glm::dvec3(0.0, 0.0, -1.0) + ); + glm::dvec3 centerPos = _focusNode->worldPosition(); + glm::dvec3 directionToCenter = glm::normalize(centerPos - camPos); + + double angle = glm::angle(camDir, directionToCenter); + + // Minimum is two second. Otherwise proportional to angle + _rotateToFocusNodeInterpolator.setInterpolationTime(glm::max(angle * 2.0, 2.0)); + _rotateToFocusNodeInterpolator.start(); +} + +bool OrbitalNavigator::followingNodeRotation() const { + return _followRotationInterpolator.value() >= 1.0; +} + +SceneGraphNode* OrbitalNavigator::focusNode() const { + return _focusNode; +} + +OrbitalNavigator::CameraRotationDecomposition + OrbitalNavigator::decomposeCameraRotation( + const glm::dvec3& cameraPosition, + const glm::dquat& cameraRotation, + const glm::dvec3& cameraLookUp, + const glm::dvec3& cameraViewDirection) +{ + glm::dmat4 inverseModelTransform = _focusNode->inverseModelTransform(); + glm::dmat4 modelTransform = _focusNode->modelTransform(); + glm::dvec3 cameraPositionModelSpace = + glm::dvec3(inverseModelTransform * glm::dvec4(cameraPosition, 1)); + + SurfacePositionHandle posHandle = + _focusNode->calculateSurfacePositionHandle(cameraPositionModelSpace); + + glm::dvec3 directionFromSurfaceToCameraModelSpace = + posHandle.referenceSurfaceOutDirection; + glm::dvec3 directionFromSurfaceToCamera = + glm::normalize(glm::dmat3(modelTransform) * + directionFromSurfaceToCameraModelSpace); + + // To avoid problem with lookup in up direction we adjust is with the view direction + glm::dmat4 lookAtMat = glm::lookAt( + glm::dvec3(0.0, 0.0, 0.0), + -directionFromSurfaceToCamera, + normalize(cameraViewDirection + cameraLookUp)); + glm::dquat globalCameraRotation = glm::normalize(glm::quat_cast(inverse(lookAtMat))); + glm::dquat localCameraRotation = glm::inverse(globalCameraRotation) * cameraRotation; + + return { localCameraRotation, globalCameraRotation }; +} + +glm::dquat OrbitalNavigator::roll(double deltaTime, + const glm::dquat& localCameraRotation) const +{ + glm::dquat rollQuat = glm::angleAxis( + _mouseStates.localRollMouseVelocity().x * deltaTime, + glm::dvec3(0.0, 0.0, 1.0) + ); + return localCameraRotation * rollQuat; +} + +glm::dquat OrbitalNavigator::rotateLocally(double deltaTime, + const glm::dquat& localCameraRotation) const +{ + glm::dvec3 eulerAngles( + _mouseStates.localRotationMouseVelocity().y, + _mouseStates.localRotationMouseVelocity().x, + 0.0 + ); + glm::dquat rotationDiff = glm::dquat(eulerAngles * deltaTime); + return localCameraRotation * rotationDiff; +} + +glm::dquat OrbitalNavigator::interpolateLocalRotation( + double deltaTime, + const glm::dquat& localCameraRotation) +{ + if (_rotateToFocusNodeInterpolator.isInterpolating()) { + double t = _rotateToFocusNodeInterpolator.value(); + _rotateToFocusNodeInterpolator.setDeltaTime(deltaTime); + _rotateToFocusNodeInterpolator.step(); + glm::dquat result = glm::slerp( + localCameraRotation, + glm::dquat(glm::dvec3(0.0)), + glm::min(t * _rotateToFocusNodeInterpolator.deltaTimeScaled(), 1.0)); + if (angle(result) < 0.01) { + _rotateToFocusNodeInterpolator.end(); + } + return result; + } + else { + return localCameraRotation; + } +} + +glm::dvec3 OrbitalNavigator::translateHorizontally( + double deltaTime, + const glm::dvec3& cameraPosition, + const glm::dvec3& objectPosition, + const glm::dquat& focusNodeRotationDiff, + const glm::dquat& globalCameraRotation, + const SurfacePositionHandle& positionHandle) const +{ + glm::dmat4 modelTransform = _focusNode->modelTransform(); + + glm::dvec3 outDirection = + glm::normalize(glm::dmat3(modelTransform) * + positionHandle.referenceSurfaceOutDirection); + + // Vector logic + glm::dvec3 posDiff = cameraPosition - objectPosition; + glm::dvec3 centerToReferenceSurface = glm::dmat3(modelTransform) * + positionHandle.centerToReferenceSurface; + glm::dvec3 centerToActualSurfaceModelSpace = positionHandle.centerToReferenceSurface + + positionHandle.referenceSurfaceOutDirection * positionHandle.heightToSurface; + glm::dvec3 centerToActualSurface = glm::dmat3(modelTransform) * + centerToActualSurfaceModelSpace; + glm::dvec3 actualSurfaceToCamera = posDiff - centerToActualSurface; + double distFromSurfaceToCamera = glm::length(actualSurfaceToCamera); + + // Final values to be used + double distFromCenterToSurface = length(centerToActualSurface); + double distFromCenterToCamera = length(posDiff); + + double speedScale = + distFromCenterToSurface > 0.0 ? + glm::clamp(distFromSurfaceToCamera / distFromCenterToSurface, 0.0, 1.0) : + 1.0; + + // Get rotation in camera space + glm::dvec3 eulerAngles = glm::dvec3( + -_mouseStates.globalRotationMouseVelocity().y * deltaTime, + -_mouseStates.globalRotationMouseVelocity().x * deltaTime, + 0) * speedScale; + glm::dquat rotationDiffCamSpace = glm::dquat(eulerAngles); + + // Transform to world space + glm::dquat rotationDiffWorldSpace = + globalCameraRotation * + rotationDiffCamSpace * + glm::inverse(globalCameraRotation); + + // Rotate and find the difference vector + glm::dvec3 rotationDiffVec3 = + (distFromCenterToCamera * outDirection) + * rotationDiffWorldSpace + - (distFromCenterToCamera * outDirection); + + // Add difference to position + return cameraPosition + rotationDiffVec3; +} + +glm::dvec3 OrbitalNavigator::followFocusNodeRotation( + const glm::dvec3& cameraPosition, + const glm::dvec3& objectPosition, + const glm::dquat& focusNodeRotationDiff) const +{ + glm::dvec3 posDiff = cameraPosition - objectPosition; + glm::dvec3 rotationDiffVec3AroundCenter = + posDiff + * focusNodeRotationDiff + - (posDiff); + return cameraPosition + rotationDiffVec3AroundCenter; +} + +glm::dquat OrbitalNavigator::rotateGlobally( + const glm::dquat& globalCameraRotation, + const glm::dvec3& objectPosition, + const glm::dquat& focusNodeRotationDiff, + const glm::dvec3& cameraPosition, + const SurfacePositionHandle& positionHandle) const +{ + glm::dmat4 modelTransform = _focusNode->modelTransform(); + + glm::dvec3 directionFromSurfaceToCamera = + glm::dmat3(modelTransform) * positionHandle.referenceSurfaceOutDirection; + + glm::dvec3 lookUpWhenFacingSurface = + glm::inverse(focusNodeRotationDiff) * + globalCameraRotation * glm::dvec3(0.0, 1.0, 0.0); + glm::dmat4 lookAtMat = glm::lookAt( + glm::dvec3(0.0, 0.0, 0.0), + -directionFromSurfaceToCamera, + lookUpWhenFacingSurface); + return glm::normalize(glm::quat_cast(glm::inverse(lookAtMat))); +} + +glm::dvec3 OrbitalNavigator::translateVertically( + double deltaTime, + const glm::dvec3& cameraPosition, + const glm::dvec3& objectPosition, + const SurfacePositionHandle& positionHandle) const +{ + glm::dmat4 modelTransform = _focusNode->modelTransform(); + + glm::dvec3 posDiff = cameraPosition - objectPosition; + + glm::dvec3 centerToReferenceSurface = + glm::dmat3(modelTransform) * positionHandle.centerToReferenceSurface; + glm::dvec3 centerToActualSurfaceModelSpace = + positionHandle.centerToReferenceSurface + + positionHandle.referenceSurfaceOutDirection * positionHandle.heightToSurface; + glm::dvec3 centerToActualSurface = + glm::dmat3(modelTransform) * centerToActualSurfaceModelSpace; + glm::dvec3 actualSurfaceToCamera = posDiff - centerToActualSurface; + + return cameraPosition - + actualSurfaceToCamera * _mouseStates.truckMovementMouseVelocity().y * deltaTime; +} + +glm::dquat OrbitalNavigator::rotateHorizontally( + double deltaTime, + const glm::dquat& globalCameraRotation, + const glm::dvec3& cameraPosition, + const SurfacePositionHandle& positionHandle) const +{ + glm::dmat4 modelTransform = _focusNode->modelTransform(); + + glm::dvec3 directionFromSurfaceToCameraModelSpace = + positionHandle.referenceSurfaceOutDirection; + glm::dvec3 directionFromSurfaceToCamera = + glm::normalize(glm::dmat3(modelTransform) * directionFromSurfaceToCameraModelSpace); + + glm::dquat cameraRollRotation = + glm::angleAxis( + _mouseStates.globalRollMouseVelocity().x * + deltaTime, directionFromSurfaceToCamera + ); + return cameraRollRotation * globalCameraRotation; +} + +glm::dvec3 OrbitalNavigator::pushToSurface( + double minHeightAboveGround, + const glm::dvec3& cameraPosition, + const glm::dvec3& objectPosition, + const SurfacePositionHandle& positionHandle) const +{ + glm::dmat4 modelTransform = _focusNode->modelTransform(); + + glm::dvec3 posDiff = cameraPosition - objectPosition; + glm::dvec3 referenceSurfaceOutDirection = + glm::dmat3(modelTransform) * positionHandle.referenceSurfaceOutDirection; + glm::dvec3 centerToReferenceSurface = + glm::dmat3(modelTransform) * positionHandle.centerToReferenceSurface; + glm::dvec3 centerToActualSurfaceModelSpace = + positionHandle.centerToReferenceSurface + + positionHandle.referenceSurfaceOutDirection * positionHandle.heightToSurface; + glm::dvec3 centerToActualSurface = + glm::dmat3(modelTransform) * centerToActualSurfaceModelSpace; + glm::dvec3 actualSurfaceToCamera = posDiff - centerToActualSurface; + double surfaceToCameraSigned = + glm::length(actualSurfaceToCamera) * + glm::sign(dot(actualSurfaceToCamera, referenceSurfaceOutDirection)); + + return cameraPosition + referenceSurfaceOutDirection * + glm::max(minHeightAboveGround - surfaceToCameraSigned, 0.0); +} + +glm::dquat OrbitalNavigator::interpolateRotationDifferential( + double deltaTime, + double interpolationTime, + const glm::dquat& rotationDiff, + const glm::dvec3& objectPosition, + const glm::dvec3& cameraPosition, + const SurfacePositionHandle& positionHandle) +{ + glm::dmat4 modelTransform = _focusNode->modelTransform(); + + double maximumDistanceForRotation = glm::length( + glm::dmat3(modelTransform) * positionHandle.centerToReferenceSurface + ) * _followFocusNodeRotationDistance; + double distanceToCamera = glm::length(cameraPosition - objectPosition); + + // Interpolate with a negative delta time if distance is too large to follow + double interpolationSign = glm::sign(maximumDistanceForRotation - distanceToCamera); + + _followRotationInterpolator.setInterpolationTime(interpolationTime); + _followRotationInterpolator.setDeltaTime(interpolationSign * deltaTime); + _followRotationInterpolator.step(); + + return glm::slerp( + glm::dquat(glm::dvec3(0.0)), + rotationDiff, + _followRotationInterpolator.value() + ); +} + +SurfacePositionHandle OrbitalNavigator::calculateSurfacePositionHandle( + const glm::dvec3 cameraPositionWorldSpace) +{ + glm::dmat4 inverseModelTransform = _focusNode->inverseModelTransform(); + glm::dvec3 cameraPositionModelSpace = + glm::dvec3(inverseModelTransform * glm::dvec4(cameraPositionWorldSpace, 1)); + SurfacePositionHandle posHandle = + _focusNode->calculateSurfacePositionHandle(cameraPositionModelSpace); + return posHandle; +} + +} // namespace interaction +} // namespace openspace diff --git a/src/network/parallelconnection.cpp b/src/network/parallelconnection.cpp index 18ef602172..71c8f603ad 100644 --- a/src/network/parallelconnection.cpp +++ b/src/network/parallelconnection.cpp @@ -66,8 +66,8 @@ #include #include #include -#include -#include +#include +#include #include #include #include @@ -580,14 +580,16 @@ void ParallelConnection::dataMessageReceived(const std::vector& messageCon datamessagestructures::CameraKeyframe kf(buffer); kf._timestamp = calculateBufferedKeyframeTime(kf._timestamp); - OsEng.interactionHandler().removeKeyframesAfter(kf._timestamp); - interaction::KeyframeInteractionMode::CameraPose pose; + OsEng.navigationHandler().keyframeNavigator().removeKeyframesAfter( + kf._timestamp); + interaction::KeyframeNavigator::CameraPose pose; pose.focusNode = kf._focusNode; pose.position = kf._position; pose.rotation = kf._rotation; pose.followFocusNodeRotation = kf._followNodeRotation; - OsEng.interactionHandler().addKeyframe(kf._timestamp, pose); + OsEng.navigationHandler().keyframeNavigator().addKeyframe( + kf._timestamp, pose); break; } case datamessagestructures::Type::TimeData: { @@ -755,7 +757,7 @@ void ParallelConnection::connectionStatusMessageReceived(const std::vector setStatus(status); - OsEng.interactionHandler().clearKeyframes(); + OsEng.navigationHandler().keyframeNavigator().clearKeyframes(); OsEng.timeManager().clearKeyframes(); } @@ -1012,7 +1014,7 @@ void ParallelConnection::sendScript(std::string script) { } void ParallelConnection::resetTimeOffset() { - OsEng.interactionHandler().clearKeyframes(); + OsEng.navigationHandler().keyframeNavigator().clearKeyframes(); OsEng.timeManager().clearKeyframes(); std::lock_guard latencyLock(_latencyMutex); _latencyDiffs.clear(); @@ -1082,21 +1084,21 @@ const std::string& ParallelConnection::hostName() { } void ParallelConnection::sendCameraKeyframe() { - SceneGraphNode* focusNode = OsEng.interactionHandler().focusNode(); + SceneGraphNode* focusNode = OsEng.navigationHandler().focusNode(); if (!focusNode) { return; } // Create a keyframe with current position and orientation of camera datamessagestructures::CameraKeyframe kf; - kf._position = OsEng.interactionHandler().focusNodeToCameraVector(); + kf._position = OsEng.navigationHandler().focusNodeToCameraVector(); - kf._followNodeRotation = OsEng.interactionHandler().interactionMode()->followingNodeRotation(); + kf._followNodeRotation = OsEng.navigationHandler().orbitalNavigator().followingNodeRotation(); if (kf._followNodeRotation) { kf._position = glm::inverse(focusNode->worldRotationMatrix()) * kf._position; - kf._rotation = OsEng.interactionHandler().focusNodeToCameraRotation(); + kf._rotation = OsEng.navigationHandler().focusNodeToCameraRotation(); } else { - kf._rotation = OsEng.interactionHandler().camera()->rotationQuaternion(); + kf._rotation = OsEng.navigationHandler().camera()->rotationQuaternion(); } kf._focusNode = focusNode->name(); diff --git a/src/query/query.cpp b/src/query/query.cpp index dadc2bc922..e2d205441a 100644 --- a/src/query/query.cpp +++ b/src/query/query.cpp @@ -26,7 +26,7 @@ #include #include -#include +#include #include #include #include diff --git a/src/rendering/renderable.cpp b/src/rendering/renderable.cpp index bc3e2e7880..de6fa7e52f 100644 --- a/src/rendering/renderable.cpp +++ b/src/rendering/renderable.cpp @@ -158,6 +158,17 @@ void Renderable::render(const RenderData& data, RendererTasks&) { void Renderable::render(const RenderData&) {} +SurfacePositionHandle Renderable::calculateSurfacePositionHandle( + const glm::dvec3& targetModelSpace) +{ + glm::dvec3 directionFromCenterToTarget = glm::normalize(targetModelSpace); + return { + directionFromCenterToTarget * static_cast(boundingSphere()), + directionFromCenterToTarget, + 0.0 + }; +} + void Renderable::setPscUniforms(ghoul::opengl::ProgramObject& program, const Camera& camera, const PowerScaledCoordinate& position) diff --git a/src/rendering/renderengine.cpp b/src/rendering/renderengine.cpp index 65314e2d71..8d75ff175f 100644 --- a/src/rendering/renderengine.cpp +++ b/src/rendering/renderengine.cpp @@ -32,7 +32,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/src/scene/scene.cpp b/src/scene/scene.cpp index 5147d0bde0..2f65a7b5d7 100644 --- a/src/scene/scene.cpp +++ b/src/scene/scene.cpp @@ -28,7 +28,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/src/scene/scenegraphnode.cpp b/src/scene/scenegraphnode.cpp index 24d30087ab..9e0bc937ec 100644 --- a/src/scene/scenegraphnode.cpp +++ b/src/scene/scenegraphnode.cpp @@ -477,6 +477,21 @@ void SceneGraphNode::setDependencies(const std::vector& depende } } +SurfacePositionHandle SceneGraphNode::calculateSurfacePositionHandle( + const glm::dvec3& targetModelSpace) +{ + if (_renderable) { + return _renderable->calculateSurfacePositionHandle(targetModelSpace); + } + else { + return { + glm::dvec3(0.0, 0.0, 0.0), + glm::normalize(targetModelSpace), + 0.0 + }; + } +} + const std::vector& SceneGraphNode::dependencies() const { return _dependencies; } diff --git a/src/util/camera.cpp b/src/util/camera.cpp index a7ecb6b0c9..f185fb4699 100644 --- a/src/util/camera.cpp +++ b/src/util/camera.cpp @@ -27,7 +27,6 @@ #include #include #include -#include #include