diff --git a/apps/OpenSpace/main.cpp b/apps/OpenSpace/main.cpp index b97d159420..765ce7f5df 100644 --- a/apps/OpenSpace/main.cpp +++ b/apps/OpenSpace/main.cpp @@ -74,6 +74,8 @@ namespace { constexpr const char* _loggerCat = "main"; sgct::Engine* SgctEngine; +openspace::interaction::JoystickInputStates joystickInputStates; + constexpr const char* OpenVRTag = "OpenVR"; constexpr const char* SpoutTag = "Spout"; @@ -345,12 +347,99 @@ void mainInitFunc() { } } + OsEng.setJoystickInputStates(joystickInputStates); + LTRACE("main::mainInitFunc(end)"); } void mainPreSyncFunc() { LTRACE("main::mainPreSyncFunc(begin)"); OsEng.preSynchronization(); + + // Query joystick status + using namespace openspace::interaction; + + for (int i = GLFW_JOYSTICK_1; i <= GLFW_JOYSTICK_LAST; ++i) { + JoystickInputState& state = joystickInputStates[i]; + + int present = glfwJoystickPresent(i); + if (present == GLFW_TRUE) { + if (!state.isConnected) { + // Joystick was added + state.isConnected = true; + state.name = SgctEngine->getJoystickName(i); + + std::fill(state.axes.begin(), state.axes.end(), 0.f); + std::fill( + state.buttons.begin(), + state.buttons.end(), + JoystickAction::Idle + ); + } + + const float* axes = SgctEngine->getJoystickAxes(i, &state.nAxes); + if (state.nAxes > JoystickInputState::MaxAxes) { + LWARNING(fmt::format( + "Joystick/Gamepad {} has {} axes, but only {} axes are supported. " + "All excess axes are ignored", + state.name, + state.nAxes, + JoystickInputState::MaxAxes + )); + state.nAxes = JoystickInputState::MaxAxes; + } + std::memcpy(state.axes.data(), axes, state.nAxes * sizeof(float)); + + const unsigned char* buttons = SgctEngine->getJoystickButtons( + i, + &state.nButtons + ); + + if (state.nButtons > JoystickInputState::MaxButtons) { + LWARNING(fmt::format( + "Joystick/Gamepad {} has {} buttons, but only {} buttons are " + "supported. All excess buttons are ignored", + state.name, + state.nButtons, + JoystickInputState::MaxButtons + )); + state.nButtons = JoystickInputState::MaxButtons; + } + + for (int j = 0; j < state.nButtons; ++j) { + bool currentlyPressed = buttons[j] == GLFW_PRESS; + + if (currentlyPressed) { + switch (state.buttons[j]) { + case JoystickAction::Idle: + case JoystickAction::Release: + state.buttons[j] = JoystickAction::Press; + break; + case JoystickAction::Press: + case JoystickAction::Repeat: + state.buttons[j] = JoystickAction::Repeat; + break; + } + } + else { + switch (state.buttons[j]) { + case JoystickAction::Idle: + case JoystickAction::Release: + state.buttons[j] = JoystickAction::Idle; + break; + case JoystickAction::Press: + case JoystickAction::Repeat: + state.buttons[j] = JoystickAction::Release; + break; + } + } + } + } + else { + state.isConnected = false; + } + } + LTRACE("main::mainPreSyncFunc(end)"); } diff --git a/data/assets/default.scene b/data/assets/default.scene index aa54ad4635..d5b4467a9c 100644 --- a/data/assets/default.scene +++ b/data/assets/default.scene @@ -12,6 +12,7 @@ assetHelper.requestAll(asset, 'scene/digitaluniverse') -- Load default key bindings applicable to most scenes asset.require('util/default_keybindings') asset.require('util/default_dashboard') +asset.require('util/default_joystick') asset.request('customization/globebrowsing') diff --git a/data/assets/util/default_joystick.asset b/data/assets/util/default_joystick.asset new file mode 100644 index 0000000000..64be2b36c3 --- /dev/null +++ b/data/assets/util/default_joystick.asset @@ -0,0 +1,136 @@ +local propertyHelper = asset.require('./property_helper') + +-- Allowed values for the second parameter of bindJoystickAxis: +-- "None" +-- "Orbit X" +-- "Orbit Y" +-- "Zoom In" +-- "Zoom Out" +-- "LocalRoll X" +-- "LocalRoll Y" +-- "GlobalRoll X" +-- "GlobalRoll Y" +-- "Pan X" +-- "Pan Y" +-- Third parameter determines whether the axis should be inverted +-- Fourth parameter determines whether the axis should be normalized from [-1,1] to [0,1] + + +local XBoxController = { + LeftThumbStick = { 0 , 1 }, + RightThumbStick = { 2, 3 }, + LeftTrigger = 4, + RightTrigger = 5, + A = 0, + B = 1, + X = 2, + Y = 3, + LB = 4, + RB = 5, + Select = 6, + Start = 7, + LeftStick = 8, + RightStick = 9, + DPad = { + Up = 10, + Right = 11, + Down = 12, + Left = 13 + } +} + +local PS4Controller = { + LeftThumbStick = { 0 , 1 }, + RightThumbStick = { 2, 5 }, + LeftTrigger = 3, + RightTrigger = 4, + A = 3, -- Triangle + B = 0, -- Square + X = 2, -- Circle + Y = 1, -- Cross + LB = 4, + RB = 5, + Select = 9, -- options + Start = 12, -- PS button + LeftStick = 10, + RightStick = 11, + DPad = { + Up = 14, + Right = 15, + Down = 16, + Left = 17 + } +} + +-- Variables to store the state of the joystick between frames +Joystick = {} +Joystick.State = {} +Joystick.State.IsInRollMode = false +Joystick.State.Axis = {} + +local bindLocalRoll = function(axis) +return [[ + -- We only want to store the current state in the first mode that is enabled, otherwise we will overwrite the backup + if not Joystick.State.IsInRollMode then + -- Save current axis state + Joystick.State.Axis.Type, Joystick.State.Axis.Inverted, Joystick.State.Axis.Normalized = openspace.navigation.joystickAxis(]] .. axis .. [[) + end + + -- Set new axis state + openspace.navigation.bindJoystickAxis(]] .. axis .. [[, "LocalRoll X", true); + Joystick.State.IsInRollMode = true + ]] +end + +local bindGlobalRoll = function(axis) + return [[ + -- We only want to store the current state in the first mode that is enabled, otherwise we will overwrite the backup + if not Joystick.State.IsInRollMode then + -- Save current axis state + Joystick.State.Axis.Type, Joystick.State.Axis.Inverted, Joystick.State.Axis.Normalized = openspace.navigation.joystickAxis(]] .. axis .. [[) + end + + -- Set new axis state + openspace.navigation.bindJoystickAxis(]] .. axis .. [[, "GlobalRoll X", true); + Joystick.State.IsInRollMode = true + ]] +end + +local unbindRoll = function(axis) + return [[ + -- Reset previous state + openspace.navigation.bindJoystickAxis(]] .. axis .. [[, Joystick.State.Axis.Type, Joystick.State.Axis.Inverted, Joystick.State.Axis.Normalized); +]] +end + +asset.onInitialize(function() + -- Set the controller to the connected controller + -- Currently: XBoxController or PS4Controller + local controller = XBoxController; + + openspace.navigation.setAxisDeadZone(controller.LeftThumbStick[1], 0.05) + openspace.navigation.setAxisDeadZone(controller.LeftThumbStick[2], 0.05) + openspace.navigation.setAxisDeadZone(controller.RightThumbStick[1], 0.05) + openspace.navigation.setAxisDeadZone(controller.RightThumbStick[2], 0.05) + openspace.navigation.setAxisDeadZone(controller.LeftTrigger, 0.05) + openspace.navigation.setAxisDeadZone(controller.RightTrigger, 0.05) + + openspace.navigation.bindJoystickAxis(controller.LeftThumbStick[1], "Orbit X"); + openspace.navigation.bindJoystickAxis(controller.LeftThumbStick[2], "Orbit Y", true); + openspace.navigation.bindJoystickAxis(controller.RightThumbStick[1], "Pan X", true); + openspace.navigation.bindJoystickAxis(controller.RightThumbStick[2], "Pan Y"); + openspace.navigation.bindJoystickAxis(controller.LeftTrigger, "Zoom Out", false, true); + openspace.navigation.bindJoystickAxis(controller.RightTrigger, "Zoom In", false, true); + + openspace.navigation.bindJoystickButton(controller.LB, bindLocalRoll(controller.RightThumbStick[1])) + openspace.navigation.bindJoystickButton(controller.LB, unbindRoll(controller.RightThumbStick[1]), "Release") + openspace.navigation.bindJoystickButton(controller.RB, bindGlobalRoll(controller.RightThumbStick[1])) + openspace.navigation.bindJoystickButton(controller.RB, unbindRoll(controller.RightThumbStick[1]), "Release") + + openspace.navigation.bindJoystickButton(controller.A, propertyHelper.invert('NavigationHandler.OrbitalNavigator.Friction.ZoomFriction')) + openspace.navigation.bindJoystickButton(controller.B, propertyHelper.invert('NavigationHandler.OrbitalNavigator.Friction.RotationalFriction')) + openspace.navigation.bindJoystickButton(controller.DPad.Left, propertyHelper.invert('NavigationHandler.OrbitalNavigator.Friction.RollFriction')) + + openspace.navigation.bindJoystickButton(controller.X, "openspace.setPropertyValue('NavigationHandler.Origin', 'Earth')") + openspace.navigation.bindJoystickButton(controller.Y, "openspace.setPropertyValue('NavigationHandler.Origin', 'Mars')") +end) diff --git a/include/openspace/engine/openspaceengine.h b/include/openspace/engine/openspaceengine.h index 8b2e3e49aa..400aba618a 100644 --- a/include/openspace/engine/openspaceengine.h +++ b/include/openspace/engine/openspaceengine.h @@ -25,6 +25,7 @@ #ifndef __OPENSPACE_CORE___OPENSPACEENGINE___H__ #define __OPENSPACE_CORE___OPENSPACEENGINE___H__ +#include #include #include #include @@ -60,9 +61,9 @@ class VirtualPropertyManager; class WindowWrapper; namespace interaction { - class NavigationHandler; class KeyBindingManager; -} + class NavigationHandler; +} // namespace interaction namespace gui { class GUI; } namespace properties { class PropertyOwner; } namespace scripting { @@ -111,6 +112,7 @@ public: void mouseButtonCallback(MouseButton button, MouseAction action); void mousePositionCallback(double x, double y); void mouseScrollWheelCallback(double posX, double posY); + void setJoystickInputStates(interaction::JoystickInputStates& states); void externalControlCallback(const char* receivedChars, int size, int clientId); void encode(); void decode(); diff --git a/include/openspace/interaction/mousestate.h b/include/openspace/interaction/camerainteractionstates.h similarity index 70% rename from include/openspace/interaction/mousestate.h rename to include/openspace/interaction/camerainteractionstates.h index 91cebb327b..abf4abff56 100644 --- a/include/openspace/interaction/mousestate.h +++ b/include/openspace/interaction/camerainteractionstates.h @@ -22,56 +22,60 @@ * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * ****************************************************************************************/ -#ifndef __OPENSPACE_CORE___MOUSESTATE___H__ -#define __OPENSPACE_CORE___MOUSESTATE___H__ +#ifndef __OPENSPACE_CORE___INPUTDEVICESTATES___H__ +#define __OPENSPACE_CORE___INPUTDEVICESTATES___H__ #include -#include - #include namespace openspace::interaction { -struct MouseState { - MouseState(double scaleFactor); - void setFriction(double friction); - void setVelocityScaleFactor(double scaleFactor); +class InputState; - glm::dvec2 previousPosition; - DelayedVariable velocity; -}; - -class MouseStates { +class CameraInteractionStates { 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); + CameraInteractionStates(double sensitivity, double velocityScaleFactor); + virtual ~CameraInteractionStates() = default; + + virtual void updateStateFromInput(const InputState& inputState, double deltaTime) = 0; + 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; + glm::dvec2 globalRotationVelocity() const; + glm::dvec2 localRotationVelocity() const; + glm::dvec2 truckMovementVelocity() const; + glm::dvec2 localRollVelocity() const; + glm::dvec2 globalRollVelocity() const; + +protected: + struct InteractionState { + InteractionState(double scaleFactor); + void setFriction(double friction); + void setVelocityScaleFactor(double scaleFactor); + + glm::dvec2 previousPosition; + DelayedVariable velocity; + }; + -private: double _sensitivity; - MouseState _globalRotationMouseState; - MouseState _localRotationMouseState; - MouseState _truckMovementMouseState; - MouseState _localRollMouseState; - MouseState _globalRollMouseState; + InteractionState _globalRotationState; + InteractionState _localRotationState; + InteractionState _truckMovementState; + InteractionState _localRollState; + InteractionState _globalRollState; }; } // namespace openspace::interaction -#endif // __OPENSPACE_CORE___MOUSESTATE___H__ +#endif // __OPENSPACE_CORE___INPUTDEVICESTATES___H__ diff --git a/include/openspace/interaction/inputdevicestates.h b/include/openspace/interaction/inputdevicestates.h new file mode 100644 index 0000000000..11db6993a0 --- /dev/null +++ b/include/openspace/interaction/inputdevicestates.h @@ -0,0 +1,81 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2018 * + * * + * 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___CAMERAINTERACTIONSTATES___H__ +#define __OPENSPACE_CORE___CAMERAINTERACTIONSTATES___H__ + +#include +#include + +namespace openspace::interaction { + +class InputState; + +class CameraInteractionStates { +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. + */ + CameraInteractionStates(double sensitivity, double velocityScaleFactor); + virtual ~CameraInteractionStates() = default; + + virtual void updateStateFromInput(const InputState& inputState, double deltaTime) = 0; + + void setRotationalFriction(double friction); + void setHorizontalFriction(double friction); + void setVerticalFriction(double friction); + void setSensitivity(double sensitivity); + void setVelocityScaleFactor(double scaleFactor); + + glm::dvec2 globalRotationVelocity() const; + glm::dvec2 localRotationVelocity() const; + glm::dvec2 truckMovementVelocity() const; + glm::dvec2 localRollVelocity() const; + glm::dvec2 globalRollVelocity() const; + +protected: + struct InteractionState { + InteractionState(double scaleFactor); + void setFriction(double friction); + void setVelocityScaleFactor(double scaleFactor); + + glm::dvec2 previousPosition; + DelayedVariable velocity; + }; + + + double _sensitivity; + + InteractionState _globalRotationState; + InteractionState _localRotationState; + InteractionState _truckMovementState; + InteractionState _localRollState; + InteractionState _globalRollState; +}; + +} // namespace openspace::interaction + +#endif // __OPENSPACE_CORE___CAMERAINTERACTIONSTATES___H__ diff --git a/include/openspace/interaction/inputstate.h b/include/openspace/interaction/inputstate.h index 7475a7942f..9ab85ffe8f 100644 --- a/include/openspace/interaction/inputstate.h +++ b/include/openspace/interaction/inputstate.h @@ -25,15 +25,15 @@ #ifndef __OPENSPACE_CORE___INPUTSTATE___H__ #define __OPENSPACE_CORE___INPUTSTATE___H__ +#include #include #include - #include - #include namespace openspace::interaction { +// This class represents the global input state of interaction devices class InputState { public: InputState() = default; @@ -45,22 +45,34 @@ public: 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; + void setJoystickInputStates(JoystickInputStates& states); + // Accessors + const std::list>& pressedKeys() const; bool isKeyPressed(std::pair keyModPair) const; bool isKeyPressed(Key key) const; + + const std::list& pressedMouseButtons() const; + glm::dvec2 mousePosition() const; + double mouseScrollDelta() const; bool isMouseButtonPressed(MouseButton mouseButton) const; + const JoystickInputStates& joystickInputStates() const; + float joystickAxis(int i) const; + bool joystickButton(int i) const; + private: - // Input from keyboard and mouse + // Input from keyboard std::list> _keysDown; + + // Input from mouse std::list _mouseButtonsDown; glm::dvec2 _mousePosition; double _mouseScrollDelta; + + // Input from joysticks + // The memory is owned by the outer most main (apps/OpenSpace/main.cpp right now) + JoystickInputStates* _joystickInputStates = nullptr; }; } // namespace openspace::interaction diff --git a/include/openspace/interaction/joystickcamerastates.h b/include/openspace/interaction/joystickcamerastates.h new file mode 100644 index 0000000000..89e19488ee --- /dev/null +++ b/include/openspace/interaction/joystickcamerastates.h @@ -0,0 +1,122 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2018 * + * * + * 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___JOYSTICKSTATE___H__ +#define __OPENSPACE_CORE___JOYSTICKSTATE___H__ + +#include + +#include +#include +#include +#include +#include + +namespace openspace::interaction { + +class JoystickCameraStates : public CameraInteractionStates { +public: + enum class AxisType { + None = 0, + OrbitX, + OrbitY, + ZoomIn, + ZoomOut, + LocalRollX, + LocalRollY, + GlobalRollX, + GlobalRollY, + PanX, + PanY + }; + + BooleanType(AxisInvert); + BooleanType(AxisNormalize); + BooleanType(ButtonCommandRemote); + + struct AxisInformation { + AxisType type = AxisType::None; + AxisInvert invert = AxisInvert::No; + AxisNormalize normalize = AxisNormalize::No; + + float deadzone = 0.f; + }; + + + JoystickCameraStates(double sensitivity, double velocityScaleFactor); + + void updateStateFromInput(const InputState& inputState, double deltaTime) override; + + void setAxisMapping( + int axis, + AxisType mapping, + AxisInvert shouldInvert = AxisInvert::No, + AxisNormalize shouldNormalize = AxisNormalize::No + ); + + AxisInformation axisMapping(int axis) const; + + void setDeadzone(int axis, float deadzone); + float deadzone(int axis) const; + + + void bindButtonCommand(int button, std::string command, JoystickAction action, + ButtonCommandRemote local); + void clearButtonCommand(int button); + std::vector buttonCommand(int button) const; + +private: + // We use an array for the axes and a map for the buttons since the axis are going to + // be accessed much more often and thus have to be more efficient. And storing a few + // extra AxisInformation that are not used will not matter that much; finding an axis + // location in a potential map each frame, however, would + + std::array _axisMapping; + + struct ButtonInformation { + std::string command; + JoystickAction action; + ButtonCommandRemote synchronization; + std::string documentation; + }; + + std::multimap _buttonMapping; +}; + +} // namespace openspace::interaction + +namespace std { + +std::string to_string(const openspace::interaction::JoystickCameraStates::AxisType& type); + +} // namespace std + +namespace ghoul { + +template <> +openspace::interaction::JoystickCameraStates::AxisType from_string(const std::string& string); + +} // namespace ghoul + +#endif // __OPENSPACE_CORE___JOYSTICKSTATE___H__ diff --git a/include/openspace/interaction/joystickinputstate.h b/include/openspace/interaction/joystickinputstate.h new file mode 100644 index 0000000000..baf75c43d5 --- /dev/null +++ b/include/openspace/interaction/joystickinputstate.h @@ -0,0 +1,126 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2018 * + * * + * 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___JOYSTICKINPUTSTATE___H__ +#define __OPENSPACE_CORE___JOYSTICKINPUTSTATE___H__ + +#include +#include +#include +#include + +namespace openspace::interaction { + +/** + * Actions that any button of a joystick can have. Each button must be in one of these + * states + */ +enum class JoystickAction : uint8_t { + /// Idle state if the button is unpressed and has been unpressed since last frame + Idle = 0, + /// If the button has been pressed since the last frame + Press, + /// If the button has been pressed since longer than last frame + Repeat, + /// If the button was released since the last frame + Release +}; + +/** + * The input state of a single joystick. + */ +struct JoystickInputState { + /// The maximum number of supported axes + static const int MaxAxes = 8; + /// The maximum number of supported buttons + static const int MaxButtons = 32; + + /// Marks whether this joystick is connected. If this value is \c false, all other + /// members of this struct are undefined + bool isConnected = false; + + /// The name of this joystick + std::string name; + + /// The number of axes that this joystick supports + int nAxes = 0; + /// The values for each axis. Each value is in the range [-1, 1]. Only the first + /// \c nAxes values are defined values, the rest are undefined + std::array axes; + + /// The number of buttons that this joystick possesses + int nButtons = 0; + /// The status of each button. Only the first \c nButtons values are defined, the rest + /// are undefined + std::array buttons; +}; + +/// The maximum number of joysticks that are supported by this system. This number is +/// derived from the available GLFW constants +constexpr const int MaxJoysticks = 16; +struct JoystickInputStates : public std::array { + /** + * This function adds the contributions of all connected joysticks for the provided + * \p axis. After adding each joysticks contribution, the result is clamped to [-1,1]. + * If a joystick does not possess a particular axis, it's does not contribute to the + * sum. + * + * \param axis The numerical axis for which the values are added + * \return The summed axis values of all connected joysticks + * + * \pre \p axis must be 0 or positive + */ + float axis(int axis) const; + + /** + * This functions checks whether any connected joystick has its \p button in the + * passed \p action. Any joystick that does not posses the \p button, it will be + * ignored. + * + * \param button The button that is to be checked + * \param action The action which is checked for each button + * \return \c true if there is at least one joystick whose \param button is in the + * \p action state + * + * \pre \p button must be 0 or positive + */ + bool button(int button, JoystickAction action) const; +}; + +} // namespace openspace::interaction + +namespace std { + +std::string to_string(openspace::interaction::JoystickAction action); + +} // namespace std + +namespace ghoul { + +template <> +openspace::interaction::JoystickAction from_string(const std::string& str); + +} // namespace ghoul + +#endif // __OPENSPACE_CORE___JOYSTICKSTATE___H__ diff --git a/include/openspace/interaction/keybindingmanager.h b/include/openspace/interaction/keybindingmanager.h index c4a47d7631..11138eae38 100644 --- a/include/openspace/interaction/keybindingmanager.h +++ b/include/openspace/interaction/keybindingmanager.h @@ -79,7 +79,6 @@ public: void keyboardCallback(Key key, KeyModifier modifier, KeyAction action); private: - std::string generateJson() const override; std::multimap _keyLua; diff --git a/include/openspace/interaction/controller.h b/include/openspace/interaction/mousecamerastates.h similarity index 83% rename from include/openspace/interaction/controller.h rename to include/openspace/interaction/mousecamerastates.h index 6375de8aa6..6e45af34df 100644 --- a/include/openspace/interaction/controller.h +++ b/include/openspace/interaction/mousecamerastates.h @@ -22,30 +22,20 @@ * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * ****************************************************************************************/ -#ifndef __OPENSPACE_CORE___CONTROLLER___H__ -#define __OPENSPACE_CORE___CONTROLLER___H__ +#ifndef __OPENSPACE_CORE___MOUSECAMERASTATES___H__ +#define __OPENSPACE_CORE___MOUSECAMERASTATES___H__ -#include - -#include -#include +#include namespace openspace::interaction { -class NavigationHandler; - -class Controller { +class MouseCameraStates : public CameraInteractionStates { public: - Controller() : - _handler(nullptr) - {} + MouseCameraStates(double sensitivity, double velocityScaleFactor); - void setHandler(NavigationHandler* handler); - -protected: - NavigationHandler* _handler; + void updateStateFromInput(const InputState& inputState, double deltaTime) override; }; } // namespace openspace::interaction -#endif // __OPENSPACE_CORE___CONTROLLER___H__ +#endif // __OPENSPACE_CORE___MOUSECAMERASTATES___H__ diff --git a/include/openspace/interaction/navigationhandler.h b/include/openspace/interaction/navigationhandler.h index 9ab9e08f20..e906700333 100644 --- a/include/openspace/interaction/navigationhandler.h +++ b/include/openspace/interaction/navigationhandler.h @@ -29,6 +29,8 @@ #include #include +#include +#include #include #include #include @@ -63,7 +65,7 @@ public: void updateCamera(double deltaTime); // Accessors - ghoul::Dictionary getCameraStateDictionary(); + ghoul::Dictionary cameraStateDictionary(); SceneGraphNode* focusNode() const; glm::dvec3 focusNodeToCameraVector() const; glm::quat focusNodeToCameraRotation() const; @@ -74,10 +76,33 @@ public: // Callback functions void keyboardCallback(Key key, KeyModifier modifier, KeyAction action); + void mouseButtonCallback(MouseButton button, MouseAction action); void mousePositionCallback(double x, double y); void mouseScrollWheelCallback(double pos); + void setJoystickInputStates(JoystickInputStates& states); + + void setJoystickAxisMapping( + int axis, + JoystickCameraStates::AxisType mapping, + JoystickCameraStates::AxisInvert shouldInvert = JoystickCameraStates::AxisInvert::No, + JoystickCameraStates::AxisNormalize shouldNormalize = JoystickCameraStates::AxisNormalize::No + ); + + JoystickCameraStates::AxisInformation joystickAxisMapping(int axis) const; + + void setJoystickAxisDeadzone(int axis, float deadzone); + float joystickAxisDeadzone(int axis) const; + + void bindJoystickButtonCommand(int button, std::string command, JoystickAction action, + JoystickCameraStates::ButtonCommandRemote remote); + + void clearJoystickButtonCommand(int button); + std::vector joystickButtonCommand(int button) const; + + + void saveCameraStateToFile(const std::string& filepath); void restoreCameraStateFromFile(const std::string& filepath); diff --git a/include/openspace/interaction/orbitalnavigator.h b/include/openspace/interaction/orbitalnavigator.h index 38c569b34b..b31965b517 100644 --- a/include/openspace/interaction/orbitalnavigator.h +++ b/include/openspace/interaction/orbitalnavigator.h @@ -28,7 +28,8 @@ #include #include #include -#include +#include +#include #include #include @@ -50,11 +51,14 @@ public: OrbitalNavigator(); ~OrbitalNavigator(); - void updateMouseStatesFromInput(const InputState& inputState, double deltaTime); - void updateCameraStateFromMouseStates(Camera& camera, double deltaTime); + void updateStatesFromInput(const InputState& inputState, double deltaTime); + void updateCameraStateFromStates(Camera& camera, double deltaTime); + void setFocusNode(SceneGraphNode* focusNode); void startInterpolateCameraDirection(const Camera& camera); + JoystickCameraStates& joystickStates(); + bool followingNodeRotation() const; SceneGraphNode* focusNode() const; @@ -82,9 +86,12 @@ private: properties::FloatProperty _followFocusNodeRotationDistance; properties::FloatProperty _minimumAllowedDistance; - properties::FloatProperty _sensitivity; + + properties::FloatProperty _mouseSensitivity; + properties::FloatProperty _joystickSensitivity; - MouseStates _mouseStates; + MouseCameraStates _mouseStates; + JoystickCameraStates _joystickStates; SceneGraphNode* _focusNode = nullptr; glm::dvec3 _previousFocusNodePosition; @@ -116,14 +123,14 @@ private: * \returns a local camera rotation modified with two degrees of freedom. */ glm::dquat rotateLocally(double deltaTime, - const glm::dquat& localCameraRotation) const; + 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); + const glm::dquat& localCameraRotation); /** * Translates the horizontal direction. If far from the focus object, this will @@ -132,10 +139,9 @@ private: * \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; + 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 @@ -143,35 +149,32 @@ private: * \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; + 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; + 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; + 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; + const glm::dquat& globalCameraRotation, const glm::dvec3& cameraPosition, + const SurfacePositionHandle& positionHandle) const; /** * Push the camera out to the surface of the object. @@ -179,19 +182,16 @@ private: * above the actual surface of the object */ glm::dvec3 pushToSurface(double minHeightAboveGround, - const glm::dvec3& cameraPosition, - const glm::dvec3& objectPosition, - const SurfacePositionHandle& positionHandle) const; + 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); + 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. diff --git a/include/openspace/interaction/luaconsole.h b/include/openspace/rendering/luaconsole.h similarity index 100% rename from include/openspace/interaction/luaconsole.h rename to include/openspace/rendering/luaconsole.h diff --git a/modules/imgui/CMakeLists.txt b/modules/imgui/CMakeLists.txt index f590d19d58..01a5cf9ccf 100644 --- a/modules/imgui/CMakeLists.txt +++ b/modules/imgui/CMakeLists.txt @@ -33,6 +33,7 @@ set(HEADER_FILES ${CMAKE_CURRENT_SOURCE_DIR}/include/guifilepathcomponent.h ${CMAKE_CURRENT_SOURCE_DIR}/include/guiglobebrowsingcomponent.h ${CMAKE_CURRENT_SOURCE_DIR}/include/guihelpcomponent.h + ${CMAKE_CURRENT_SOURCE_DIR}/include/guijoystickcomponent.h ${CMAKE_CURRENT_SOURCE_DIR}/include/guimissioncomponent.h ${CMAKE_CURRENT_SOURCE_DIR}/include/guiperformancecomponent.h ${CMAKE_CURRENT_SOURCE_DIR}/include/guiparallelcomponent.h @@ -51,6 +52,7 @@ set(SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/src/guifilepathcomponent.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/guiglobebrowsingcomponent.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/guihelpcomponent.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/guijoystickcomponent.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/guimissioncomponent.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/guiperformancecomponent.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/guiparallelcomponent.cpp diff --git a/modules/imgui/imguimodule.cpp b/modules/imgui/imguimodule.cpp index 9d1dda9c23..0f8acadadd 100644 --- a/modules/imgui/imguimodule.cpp +++ b/modules/imgui/imguimodule.cpp @@ -31,9 +31,9 @@ #include #include #include -#include #include #include +#include #include #include #include diff --git a/modules/imgui/include/gui.h b/modules/imgui/include/gui.h index f4646ec0ef..4626ea1cd6 100644 --- a/modules/imgui/include/gui.h +++ b/modules/imgui/include/gui.h @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -87,6 +88,7 @@ public: #ifdef OPENSPACE_MODULE_ISWA_ENABLED GuiIswaComponent _iswa; #endif // OPENSPACE_MODULE_ISWA_ENABLED + GuiJoystickComponent _joystick; GuiParallelComponent _parallel; GuiPropertyComponent _featuredProperties; diff --git a/src/interaction/controller.cpp b/modules/imgui/include/guijoystickcomponent.h similarity index 83% rename from src/interaction/controller.cpp rename to modules/imgui/include/guijoystickcomponent.h index 0b52cf7cf1..9b891d00e9 100644 --- a/src/interaction/controller.cpp +++ b/modules/imgui/include/guijoystickcomponent.h @@ -22,15 +22,20 @@ * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * ****************************************************************************************/ -#include +#ifndef __OPENSPACE_MODULE_IMGUI___GUIJOYSTICKCOMPONENT___H__ +#define __OPENSPACE_MODULE_IMGUI___GUIJOYSTICKCOMPONENT___H__ -#include +#include -namespace openspace::interaction { +namespace openspace::gui { -void Controller::setHandler(NavigationHandler* handler) { - _handler = handler; -} +class GuiJoystickComponent : public GuiComponent { +public: + GuiJoystickComponent(); -} // namespace openspace::interaction + void render() override; +}; +} // namespace openspace::gui + +#endif // __OPENSPACE_MODULE_IMGUI___GUIJOYSTICKCOMPONENT___H__ diff --git a/modules/imgui/src/gui.cpp b/modules/imgui/src/gui.cpp index 0e1ec88796..032080ef21 100644 --- a/modules/imgui/src/gui.cpp +++ b/modules/imgui/src/gui.cpp @@ -160,60 +160,6 @@ static void RenderDrawLists(ImDrawData* drawData) { } } - - //// Grow our buffer according to what we need - //size_t totalVertexCount = 0; - //for (int i = 0; i < nCommandLists; ++i) - // totalVertexCount += commandLists[i]->vtx_buffer.size(); - - //glBindBuffer(GL_ARRAY_BUFFER, vbo); - //size_t neededBufferSize = totalVertexCount * sizeof(ImDrawVert); - //if (neededBufferSize > vboMaxSize) { - // // Grow buffer - // vboMaxSize = neededBufferSize * 1.25f; - // glBufferData(GL_ARRAY_BUFFER, vboMaxSize, NULL, GL_STREAM_DRAW); - //} - - //// Copy and convert all vertices into a single contiguous buffer - //unsigned char* bufferData = reinterpret_cast( - // glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY) - // ); - - //if (!bufferData) { - // LFATAL("Error mapping ImGui buffer"); - // return; - //} - - //for (int i = 0; i < nCommandLists; ++i) { - // const ImDrawList* cmd_list = commandLists[i]; - // memcpy( - // bufferData, - // &cmd_list->vtx_buffer[0], - // cmd_list->vtx_buffer.size() * sizeof(ImDrawVert) - // ); - // bufferData += (cmd_list->vtx_buffer.size() * sizeof(ImDrawVert)); - //} - //glUnmapBuffer(GL_ARRAY_BUFFER); - //glBindBuffer(GL_ARRAY_BUFFER, 0); - //glBindVertexArray(vao); - - //int cmdOffset = 0; - //for (int i = 0; i < nCommandLists; ++i) { - // const ImDrawList* cmd_list = commandLists[i]; - // int vtxOffset = cmdOffset; - // for (const auto& pcmd : cmd_list->commands) { - // glScissor( - // static_cast(pcmd.clip_rect.x), - // static_cast(height - pcmd.clip_rect.w), - // static_cast(pcmd.clip_rect.z - pcmd.clip_rect.x), - // static_cast(pcmd.clip_rect.w - pcmd.clip_rect.y) - // ); - // glDrawArrays(GL_TRIANGLES, vtxOffset, pcmd.vtx_count); - // vtxOffset += pcmd.vtx_count; - // } - // cmdOffset = vtxOffset; - //} - glBindVertexArray(0); _program->deactivate(); glDisable(GL_SCISSOR_TEST); @@ -310,6 +256,7 @@ GUI::GUI() #ifdef GLOBEBROWSING_USE_GDAL addPropertySubOwner(_globeBrowsing); #endif // GLOBEBROWSING_USE_GDAL + addPropertySubOwner(_joystick); addPropertySubOwner(_filePath); addPropertySubOwner(_asset); _spaceTime.setEnabled(true); @@ -337,6 +284,7 @@ GUI::GUI() #ifdef OPENSPACE_MODULE_ISWA_ENABLED _iswa.setShowHelpTooltip(_showHelpText); #endif // OPENSPACE_MODULE_ISWA_ENABLED + _joystick.setShowHelpTooltip(_showHelpText); _parallel.setShowHelpTooltip(_showHelpText); _featuredProperties.setShowHelpTooltip(_showHelpText); }; @@ -363,6 +311,7 @@ GUI::GUI() #ifdef OPENSPACE_MODULE_ISWA_ENABLED _iswa.setShowHelpTooltipDelay(_helpTextDelay); #endif // OPENSPACE_MODULE_ISWA_ENABLED + _joystick.setShowHelpTooltipDelay(_helpTextDelay); _parallel.setShowHelpTooltipDelay(_helpTextDelay); _featuredProperties.setShowHelpTooltipDelay(_helpTextDelay); }; @@ -502,6 +451,7 @@ void GUI::initialize() { #ifdef GLOBEBROWSING_USE_GDAL _globeBrowsing.initialize(); #endif // GLOBEBROWSING_USE_GDAL + _joystick.initialize(); _performance.initialize(); _help.initialize(); _parallel.initialize(); @@ -525,6 +475,7 @@ void GUI::deinitialize() { _mission.deinitialize(); _parallel.deinitialize(); _help.deinitialize(); + _joystick.deinitialize(); _performance.deinitialize(); _globalProperty.deinitialize(); _moduleProperty.deinitialize(); @@ -629,6 +580,7 @@ void GUI::initializeGL() { _globalProperty.initializeGL(); _moduleProperty.initializeGL(); _featuredProperties.initializeGL(); + _joystick.initializeGL(); _performance.initializeGL(); _help.initializeGL(); #ifdef GLOBEBROWSING_USE_GDAL @@ -667,6 +619,7 @@ void GUI::deinitializeGL() { _performance.deinitializeGL(); _featuredProperties.deinitializeGL(); _globalProperty.deinitializeGL(); + _joystick.deinitializeGL(); _moduleProperty.deinitializeGL(); _screenSpaceProperty.deinitializeGL(); #ifdef GLOBEBROWSING_USE_GDAL @@ -744,6 +697,11 @@ void GUI::endFrame() { _iswa.render(); } #endif // OPENSPACE_MODULE_ISWA_ENABLED + + if (_joystick.isEnabled()) { + _joystick.render(); + } + if (_filePath.isEnabled()) { _filePath.render(); } @@ -883,6 +841,10 @@ void GUI::render() { ImGui::Checkbox("Mission Information", &mission); _mission.setEnabled(mission); + bool joystick = _joystick.isEnabled(); + ImGui::Checkbox("Joystick Information", &joystick); + _joystick.setEnabled(joystick); + bool filePath = _filePath.isEnabled(); ImGui::Checkbox("File Paths", &filePath); _filePath.setEnabled(filePath); diff --git a/modules/imgui/src/guijoystickcomponent.cpp b/modules/imgui/src/guijoystickcomponent.cpp new file mode 100644 index 0000000000..03c49fdbd6 --- /dev/null +++ b/modules/imgui/src/guijoystickcomponent.cpp @@ -0,0 +1,111 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2018 * + * * + * 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 + +namespace { + const ImVec2 Size = ImVec2(350, 500); +} // namespace + +namespace openspace::gui { + +GuiJoystickComponent::GuiJoystickComponent() + : GuiComponent("joystick_information", "Joystick Information") +{} + +void GuiJoystickComponent::render() { + using namespace interaction; + + ImGui::SetNextWindowCollapsed(_isCollapsed); + + bool v = _isEnabled; + ImGui::Begin("Joystick Information", &v, Size, 0.5f); + _isEnabled = v; + _isCollapsed = ImGui::IsWindowCollapsed(); + + const JoystickInputStates& states = + OsEng.navigationHandler().inputState().joystickInputStates(); + + for (int i = 0; i < states.size(); ++i) { + const JoystickInputState& state = states[i]; + if (!state.isConnected) { + continue; + } + + ImGui::Text("%s [%i]", state.name.c_str(), i); + ImGui::Text("%s", "Axes"); + for (int j = 0; j < state.nAxes; ++j) { + float f = state.axes[j]; + ImGui::SliderFloat( + std::to_string(j).c_str(), + &f, + -1.f, + 1.f + ); + } + ImGui::Text("%s", "Buttons"); + for (int j = 0; j < state.nButtons; ++j) { + ImGui::RadioButton( + std::to_string(j).c_str(), + state.buttons[j] == JoystickAction::Press || + state.buttons[j] == JoystickAction::Repeat + ); + } + + ImGui::Separator(); + } + + ImGui::Separator(); + ImGui::Separator(); + + ImGui::Text("%s", "Summed contributions"); + ImGui::Text("%s", "Axes"); + for (int i = 0; i < JoystickInputState::MaxAxes; ++i) { + float f = states.axis(i); + ImGui::SliderFloat( + std::to_string(i).c_str(), + &f, + -1.f, + 1.f + ); + } + ImGui::Text("%s", "Buttons"); + for (int i = 0; i < JoystickInputState::MaxButtons; ++i) { + ImGui::RadioButton( + std::to_string(i).c_str(), + states.button(i, JoystickAction::Press) || + states.button(i, JoystickAction::Repeat) + ); + } + + ImGui::End(); +} + +} // namespace openspace::gui diff --git a/modules/server/src/topics/getpropertytopic.cpp b/modules/server/src/topics/getpropertytopic.cpp index d9e1c2387f..c109634d41 100644 --- a/modules/server/src/topics/getpropertytopic.cpp +++ b/modules/server/src/topics/getpropertytopic.cpp @@ -24,7 +24,7 @@ #include #include -#include +#include #include #include #include diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e592f4d3e2..e1821936f3 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -42,15 +42,16 @@ set(OPENSPACE_SOURCE ${OPENSPACE_BASE_DIR}/src/engine/virtualpropertymanager.cpp ${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/camerainteractionstates.cpp ${OPENSPACE_BASE_DIR}/src/interaction/inputstate.cpp + ${OPENSPACE_BASE_DIR}/src/interaction/joystickinputstate.cpp + ${OPENSPACE_BASE_DIR}/src/interaction/joystickcamerastates.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/mousecamerastates.cpp ${OPENSPACE_BASE_DIR}/src/interaction/orbitalnavigator.cpp ${OPENSPACE_BASE_DIR}/src/mission/mission.cpp ${OPENSPACE_BASE_DIR}/src/mission/missionmanager.cpp @@ -128,6 +129,7 @@ set(OPENSPACE_SOURCE ${OPENSPACE_BASE_DIR}/src/rendering/framebufferrenderer.cpp ${OPENSPACE_BASE_DIR}/src/rendering/deferredcastermanager.cpp ${OPENSPACE_BASE_DIR}/src/rendering/loadingscreen.cpp + ${OPENSPACE_BASE_DIR}/src/rendering/luaconsole.cpp ${OPENSPACE_BASE_DIR}/src/rendering/raycastermanager.cpp ${OPENSPACE_BASE_DIR}/src/rendering/renderable.cpp ${OPENSPACE_BASE_DIR}/src/rendering/renderengine.cpp @@ -209,16 +211,17 @@ set(OPENSPACE_HEADER ${OPENSPACE_BASE_DIR}/include/openspace/engine/virtualpropertymanager.h ${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/delayedvariable.h ${OPENSPACE_BASE_DIR}/include/openspace/interaction/delayedvariable.inl + ${OPENSPACE_BASE_DIR}/include/openspace/interaction/camerainteractionstates.h ${OPENSPACE_BASE_DIR}/include/openspace/interaction/inputstate.h ${OPENSPACE_BASE_DIR}/include/openspace/interaction/interpolator.h ${OPENSPACE_BASE_DIR}/include/openspace/interaction/interpolator.inl + ${OPENSPACE_BASE_DIR}/include/openspace/interaction/joystickinputstate.h + ${OPENSPACE_BASE_DIR}/include/openspace/interaction/joystickcamerastates.h ${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/mousecamerastates.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 @@ -305,6 +308,7 @@ set(OPENSPACE_HEADER ${OPENSPACE_BASE_DIR}/include/openspace/rendering/deferredcasterlistener.h ${OPENSPACE_BASE_DIR}/include/openspace/rendering/deferredcastermanager.h ${OPENSPACE_BASE_DIR}/include/openspace/rendering/loadingscreen.h + ${OPENSPACE_BASE_DIR}/include/openspace/rendering/luaconsole.h ${OPENSPACE_BASE_DIR}/include/openspace/rendering/raycasterlistener.h ${OPENSPACE_BASE_DIR}/include/openspace/rendering/raycastermanager.h ${OPENSPACE_BASE_DIR}/include/openspace/rendering/renderable.h diff --git a/src/engine/openspaceengine.cpp b/src/engine/openspaceengine.cpp index a34b2252ac..ddbea6a0e9 100644 --- a/src/engine/openspaceengine.cpp +++ b/src/engine/openspaceengine.cpp @@ -35,9 +35,8 @@ #include #include #include -#include #include -#include +#include #include #include @@ -46,6 +45,7 @@ #include #include #include +#include #include #include #include @@ -293,7 +293,7 @@ void OpenSpaceEngine::create(int argc, char** argv, // Parse commandline arguments std::vector args(argv, argv + argc); - const std::vector& arguments = + std::vector arguments = _engine->_commandlineParser->setCommandLine(args); bool showHelp = _engine->_commandlineParser->execute(); @@ -302,8 +302,8 @@ void OpenSpaceEngine::create(int argc, char** argv, requestClose = true; return; } - std::vector argumentsCopy = arguments; - sgctArguments = std::move(argumentsCopy); + + sgctArguments = std::move(arguments); // Find configuration std::string configurationFilePath = commandlineArgumentPlaceholders.configurationName; @@ -1283,7 +1283,7 @@ void OpenSpaceEngine::preSynchronization() { } _renderEngine->updateScene(); - _navigationHandler->updateCamera(dt); + //_navigationHandler->updateCamera(dt); Camera* camera = _renderEngine->camera(); if (camera) { @@ -1510,6 +1510,10 @@ void OpenSpaceEngine::mouseScrollWheelCallback(double posX, double posY) { _navigationHandler->mouseScrollWheelCallback(posY); } +void OpenSpaceEngine::setJoystickInputStates(interaction::JoystickInputStates& states) { + _navigationHandler->setJoystickInputStates(states); +} + void OpenSpaceEngine::encode() { _syncEngine->encodeSyncables(); diff --git a/src/interaction/camerainteractionstates.cpp b/src/interaction/camerainteractionstates.cpp new file mode 100644 index 0000000000..ae0af29f03 --- /dev/null +++ b/src/interaction/camerainteractionstates.cpp @@ -0,0 +1,101 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2018 * + * * + * 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::interaction { + +CameraInteractionStates::InteractionState::InteractionState(double scaleFactor) + : previousPosition(0.0, 0.0) + , velocity(scaleFactor, 1) +{} + +void CameraInteractionStates::InteractionState::setFriction(double friction) { + velocity.setFriction(friction); +} + +void CameraInteractionStates::InteractionState::setVelocityScaleFactor(double scaleFactor) +{ + velocity.setScaleFactor(scaleFactor); +} + +CameraInteractionStates::CameraInteractionStates(double sensitivity, + double velocityScaleFactor) + : _sensitivity(sensitivity) + , _globalRotationState(velocityScaleFactor) + , _localRotationState(velocityScaleFactor) + , _truckMovementState(velocityScaleFactor) + , _localRollState(velocityScaleFactor) + , _globalRollState(velocityScaleFactor) +{} + +void CameraInteractionStates::setRotationalFriction(double friction) { + _localRotationState.setFriction(friction); + _localRollState.setFriction(friction); + _globalRollState.setFriction(friction); +} + +void CameraInteractionStates::setHorizontalFriction(double friction) { + _globalRotationState.setFriction(friction); +} + +void CameraInteractionStates::setVerticalFriction(double friction) { + _truckMovementState.setFriction(friction); +} + +void CameraInteractionStates::setSensitivity(double sensitivity) { + _sensitivity = sensitivity; +} + +void CameraInteractionStates::setVelocityScaleFactor(double scaleFactor) { + _globalRotationState.setVelocityScaleFactor(scaleFactor); + _localRotationState.setVelocityScaleFactor(scaleFactor); + _truckMovementState.setVelocityScaleFactor(scaleFactor); + _localRollState.setVelocityScaleFactor(scaleFactor); + _globalRollState.setVelocityScaleFactor(scaleFactor); +} + +glm::dvec2 CameraInteractionStates::globalRotationVelocity() const{ + return _globalRotationState.velocity.get(); +} + +glm::dvec2 CameraInteractionStates::localRotationVelocity() const{ + return _localRotationState.velocity.get(); +} + +glm::dvec2 CameraInteractionStates::truckMovementVelocity() const{ + return _truckMovementState.velocity.get(); +} + +glm::dvec2 CameraInteractionStates::localRollVelocity() const{ + return _localRollState.velocity.get(); +} + +glm::dvec2 CameraInteractionStates::globalRollVelocity() const{ + return _globalRollState.velocity.get(); +} + +} // namespace openspace::interaction diff --git a/src/interaction/inputdevicestates.cpp b/src/interaction/inputdevicestates.cpp new file mode 100644 index 0000000000..9b1f05e8a7 --- /dev/null +++ b/src/interaction/inputdevicestates.cpp @@ -0,0 +1,101 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2018 * + * * + * 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::interaction { + +CameraInteractionStates::InteractionState::InteractionState(double scaleFactor) + : previousPosition(0.0, 0.0) + , velocity(scaleFactor, 1) +{} + +void CameraInteractionStates::InteractionState::setFriction(double friction) { + velocity.setFriction(friction); +} + +void CameraInteractionStates::InteractionState::setVelocityScaleFactor(double scaleFactor) +{ + velocity.setScaleFactor(scaleFactor); +} + +CameraInteractionStates::CameraInteractionStates(double sensitivity, + double velocityScaleFactor) + : _sensitivity(sensitivity) + , _globalRotationState(velocityScaleFactor) + , _localRotationState(velocityScaleFactor) + , _truckMovementState(velocityScaleFactor) + , _localRollState(velocityScaleFactor) + , _globalRollState(velocityScaleFactor) +{} + +void CameraInteractionStates::setRotationalFriction(double friction) { + _localRotationState.setFriction(friction); + _localRollState.setFriction(friction); + _globalRollState.setFriction(friction); +} + +void CameraInteractionStates::setHorizontalFriction(double friction) { + _globalRotationState.setFriction(friction); +} + +void CameraInteractionStates::setVerticalFriction(double friction) { + _truckMovementState.setFriction(friction); +} + +void CameraInteractionStates::setSensitivity(double sensitivity) { + _sensitivity = sensitivity; +} + +void CameraInteractionStates::setVelocityScaleFactor(double scaleFactor) { + _globalRotationState.setVelocityScaleFactor(scaleFactor); + _localRotationState.setVelocityScaleFactor(scaleFactor); + _truckMovementState.setVelocityScaleFactor(scaleFactor); + _localRollState.setVelocityScaleFactor(scaleFactor); + _globalRollState.setVelocityScaleFactor(scaleFactor); +} + +glm::dvec2 CameraInteractionStates::globalRotationVelocity() const{ + return _globalRotationState.velocity.get(); +} + +glm::dvec2 CameraInteractionStates::localRotationVelocity() const{ + return _localRotationState.velocity.get(); +} + +glm::dvec2 CameraInteractionStates::truckMovementVelocity() const{ + return _truckMovementState.velocity.get(); +} + +glm::dvec2 CameraInteractionStates::localRollVelocity() const{ + return _localRollState.velocity.get(); +} + +glm::dvec2 CameraInteractionStates::globalRollVelocity() const{ + return _globalRollState.velocity.get(); +} + +} // namespace openspace::interaction diff --git a/src/interaction/inputstate.cpp b/src/interaction/inputstate.cpp index d22c2619b6..4976263f0b 100644 --- a/src/interaction/inputstate.cpp +++ b/src/interaction/inputstate.cpp @@ -24,8 +24,8 @@ #include +#include #include - #include namespace openspace::interaction { @@ -46,9 +46,7 @@ void InputState::mouseButtonCallback(MouseButton button, MouseAction action) { _mouseButtonsDown.push_back(button); } else if (action == MouseAction::Release) { - // Remove all key pressings for 'button' - _mouseButtonsDown.remove_if([button](MouseButton buttonInList) - { return button == buttonInList; }); + _mouseButtonsDown.remove(button); } } @@ -60,19 +58,23 @@ void InputState::mouseScrollWheelCallback(double mouseScrollDelta) { _mouseScrollDelta = mouseScrollDelta; } -const std::list >& InputState::getPressedKeys() const { +void InputState::setJoystickInputStates(JoystickInputStates& states) { + _joystickInputStates = &states; +} + +const std::list>& InputState::pressedKeys() const { return _keysDown; } -const std::list& InputState::getPressedMouseButtons() const { +const std::list& InputState::pressedMouseButtons() const { return _mouseButtonsDown; } -glm::dvec2 InputState::getMousePosition() const { +glm::dvec2 InputState::mousePosition() const { return _mousePosition; } -double InputState::getMouseScrollDelta() const { +double InputState::mouseScrollDelta() const { return _mouseScrollDelta; } @@ -92,4 +94,16 @@ bool InputState::isMouseButtonPressed(MouseButton mouseButton) const { mouseButton) != _mouseButtonsDown.end(); } +const JoystickInputStates& InputState::joystickInputStates() const { + return *_joystickInputStates; +} + +float InputState::joystickAxis(int i) const { + return _joystickInputStates->axis(i); +} + +bool InputState::joystickButton(int i) const{ + return _joystickInputStates->button(i, JoystickAction::Press); +} + } // namespace openspace::interaction diff --git a/src/interaction/joystickcamerastates.cpp b/src/interaction/joystickcamerastates.cpp new file mode 100644 index 0000000000..6309dfff5b --- /dev/null +++ b/src/interaction/joystickcamerastates.cpp @@ -0,0 +1,272 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2018 * + * * + * 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 + +namespace openspace::interaction { + +JoystickCameraStates::JoystickCameraStates(double sensitivity, double velocityScaleFactor) + : CameraInteractionStates(sensitivity, velocityScaleFactor) +{} + +void JoystickCameraStates::updateStateFromInput(const InputState& inputState, + double deltaTime) +{ + std::pair globalRotation = { false, glm::dvec2(0.0) }; + std::pair zoom = { false, 0.0 }; + std::pair localRoll = { false, glm::dvec2(0.0) }; + std::pair globalRoll = { false, glm::dvec2(0.0) }; + std::pair localRotation = { false, glm::dvec2(0.0) }; + + for (int i = 0; i < JoystickInputState::MaxAxes; ++i) { + AxisInformation t = _axisMapping[i]; + if (t.type == AxisType::None) { + continue; + } + + bool hasValue = true; + float value = inputState.joystickAxis(i); + + if (abs(value) <= t.deadzone) { + value = 0.f; + hasValue = false; + } + + if (t.normalize) { + value = (value + 1.f) / 2.f; + } + + if (t.invert) { + value *= -1.f; + } + + value *= _sensitivity; + + switch (t.type) { + case AxisType::None: + break; + case AxisType::OrbitX: + globalRotation.first = hasValue; + globalRotation.second.x = value; + break; + case AxisType::OrbitY: + globalRotation.first = hasValue; + globalRotation.second.y = value; + break; + case AxisType::ZoomIn: + zoom.first = hasValue; + zoom.second += value; + break; + case AxisType::ZoomOut: + zoom.first = hasValue; + zoom.second -= value; + break; + case AxisType::LocalRollX: + localRoll.first = hasValue; + localRoll.second.x = value; + break; + case AxisType::LocalRollY: + localRoll.first = hasValue; + localRoll.second.y = value; + break; + case AxisType::GlobalRollX: + globalRoll.first = hasValue; + globalRoll.second.x = value; + break; + case AxisType::GlobalRollY: + globalRoll.first = hasValue; + globalRoll.second.y = value; + break; + case AxisType::PanX: + localRotation.first = hasValue; + localRotation.second.x = value; + break; + case AxisType::PanY: + localRotation.first = hasValue; + localRotation.second.y = value; + break; + } + } + + if (globalRotation.first) { + _globalRotationState.velocity.set(globalRotation.second, deltaTime); + } + else { + _globalRotationState.velocity.decelerate(deltaTime); + } + + if (zoom.first) { + _truckMovementState.velocity.set(glm::dvec2(zoom.second), deltaTime); + } + else { + _truckMovementState.velocity.decelerate(deltaTime); + } + + if (localRoll.first) { + _localRollState.velocity.set(localRoll.second, deltaTime); + } + else { + _localRollState.velocity.decelerate(deltaTime); + } + + if (globalRoll.first) { + _globalRollState.velocity.set(globalRoll.second, deltaTime); + } + else { + _globalRollState.velocity.decelerate(deltaTime); + } + + if (localRotation.first) { + _localRotationState.velocity.set(localRotation.second, deltaTime); + } + else { + _localRotationState.velocity.decelerate(deltaTime); + } + + + for (int i = 0; i < JoystickInputState::MaxButtons; ++i) { + auto itRange = _buttonMapping.equal_range(i); + for (auto it = itRange.first; it != itRange.second; ++it) { + bool active = inputState.joystickInputStates().button(i, it->second.action); + + if (active) { + OsEng.scriptEngine().queueScript( + it->second.command, + scripting::ScriptEngine::RemoteScripting(it->second.synchronization) + ); + } + } + } +} + +void JoystickCameraStates::setAxisMapping(int axis, AxisType mapping, + AxisInvert shouldInvert, + AxisNormalize shouldNormalize) +{ + ghoul_assert(axis < JoystickInputState::MaxAxes, "axis must be < MaxAxes"); + + _axisMapping[axis] = { mapping, shouldInvert, shouldNormalize }; +} + +JoystickCameraStates::AxisInformation JoystickCameraStates::axisMapping(int axis) const { + return _axisMapping[axis]; +} + +void JoystickCameraStates::setDeadzone(int axis, float deadzone) { + _axisMapping[axis].deadzone = deadzone; +} + +float JoystickCameraStates::deadzone(int axis) const { + return _axisMapping[axis].deadzone; +} + +void JoystickCameraStates::bindButtonCommand(int button, std::string command, + JoystickAction action, + ButtonCommandRemote remote) +{ + _buttonMapping.insert({ + button, + { std::move(command), action, remote } + }); +} + +void JoystickCameraStates::clearButtonCommand(int button) { + for (auto it = _buttonMapping.begin(); it != _buttonMapping.end();) { + // If the current iterator is the button that we are looking for, delete it + // (std::multimap::erase will return the iterator to the next element for us) + if (it->first == button) { + it = _buttonMapping.erase(it); + } + else { + ++it; + } + } +} + +std::vector JoystickCameraStates::buttonCommand(int button) const { + std::vector result; + auto itRange = _buttonMapping.equal_range(button); + for (auto it = itRange.first; it != itRange.second; ++it) { + result.push_back(it->second.command); + } + return result; +} + + +} // namespace openspace::interaction + +namespace std { + +std::string to_string(const openspace::interaction::JoystickCameraStates::AxisType& type) +{ + using T = openspace::interaction::JoystickCameraStates::AxisType; + switch (type) { + case T::None: return "None"; + case T::OrbitX: return "Orbit X"; + case T::OrbitY: return "Orbit Y"; + case T::ZoomIn: return "Zoom In"; + case T::ZoomOut: return "Zoom Out"; + case T::LocalRollX: return "LocalRoll X"; + case T::LocalRollY: return "LocalRoll Y"; + case T::GlobalRollX: return "GlobalRoll X"; + case T::GlobalRollY: return "GlobalRoll Y"; + case T::PanX: return "Pan X"; + case T::PanY: return "Pan Y"; + default: return ""; + } +} + +} // namespace std + +namespace ghoul { + +template <> +openspace::interaction::JoystickCameraStates::AxisType from_string( + const std::string& string) +{ + using T = openspace::interaction::JoystickCameraStates::AxisType; + + static const std::map Map = { + { "None", T::None }, + { "Orbit X", T::OrbitX }, + { "Orbit Y", T::OrbitY }, + { "Zoom In", T::ZoomIn }, + { "Zoom Out", T::ZoomOut }, + { "LocalRoll X", T::LocalRollX }, + { "LocalRoll Y", T::LocalRollY }, + { "GlobalRoll X", T::GlobalRollX }, + { "GlobalRoll Y", T::GlobalRollY }, + { "Pan X", T::PanX }, + { "Pan Y", T::PanY } + }; + + return Map.at(string); +} + +} // namespace ghoul diff --git a/src/interaction/joystickinputstate.cpp b/src/interaction/joystickinputstate.cpp new file mode 100644 index 0000000000..a3c9ebaf5d --- /dev/null +++ b/src/interaction/joystickinputstate.cpp @@ -0,0 +1,100 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2018 * + * * + * 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 + +namespace openspace::interaction { + +float JoystickInputStates::axis(int axis) const { + ghoul_precondition(axis >= 0, "axis must be 0 or positive"); + + float res = std::accumulate( + begin(), + end(), + 0.f, + [axis](float value, const JoystickInputState& state) { + if (state.isConnected) { + value += state.axes[axis]; + } + return value; + } + ); + + // If multiple joysticks are connected, we might get values outside the -1,1 range by + // summing them up + glm::clamp(res, -1.f, 1.f); + return res; +} + +bool JoystickInputStates::button(int button, JoystickAction action) const { + ghoul_precondition(button >= 0, "button must be 0 or positive"); + + bool res = std::any_of( + begin(), + end(), + [button, action](const JoystickInputState& state) { + return state.isConnected ? (state.buttons[button] == action) : false; + } + ); + return res; +} + +} // namespace openspace::interaction + +namespace std { + +std::string to_string(openspace::interaction::JoystickAction action) { + switch (action) { + case openspace::interaction::JoystickAction::Idle: return "Idle"; + case openspace::interaction::JoystickAction::Press: return "Press"; + case openspace::interaction::JoystickAction::Repeat: return "Repeat"; + case openspace::interaction::JoystickAction::Release: return "Release"; + default: return ""; + } +} + +} // namespace std + +namespace ghoul { + +template <> +openspace::interaction::JoystickAction from_string(const std::string& string) { + static const std::map Map = { + { "Idle", openspace::interaction::JoystickAction::Idle }, + { "Press", openspace::interaction::JoystickAction::Press }, + { "Repeat", openspace::interaction::JoystickAction::Repeat }, + { "Release", openspace::interaction::JoystickAction::Release } + }; + + return Map.at(string); + +} + +} // namespace ghoul diff --git a/src/interaction/luaconsole.cpp b/src/interaction/luaconsole.cpp index d87905c31c..bb04359b78 100644 --- a/src/interaction/luaconsole.cpp +++ b/src/interaction/luaconsole.cpp @@ -22,7 +22,7 @@ * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * ****************************************************************************************/ -#include +#include #include #include diff --git a/src/interaction/mousecamerastates.cpp b/src/interaction/mousecamerastates.cpp new file mode 100644 index 0000000000..c79edcf343 --- /dev/null +++ b/src/interaction/mousecamerastates.cpp @@ -0,0 +1,125 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2018 * + * * + * 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::interaction { + +MouseCameraStates::MouseCameraStates(double sensitivity, double velocityScaleFactor) + : CameraInteractionStates(sensitivity, velocityScaleFactor) +{} + +void MouseCameraStates::updateStateFromInput(const InputState& inputState, double deltaTime) { + glm::dvec2 mousePosition = inputState.mousePosition(); + + bool button1Pressed = inputState.isMouseButtonPressed(MouseButton::Button1); + bool button2Pressed = inputState.isMouseButtonPressed(MouseButton::Button2); + bool button3Pressed = inputState.isMouseButtonPressed(MouseButton::Button3); + bool keyCtrlPressed = inputState.isKeyPressed(Key::LeftControl) | + inputState.isKeyPressed(Key::RightControl); + bool keyShiftPressed = inputState.isKeyPressed(Key::LeftShift) | + inputState.isKeyPressed(Key::RightShift); + bool keyAltPressed = inputState.isKeyPressed(Key::LeftAlt) | + inputState.isKeyPressed(Key::RightAlt); + + // Update the mouse states + if (button1Pressed && !keyShiftPressed && !keyAltPressed) { + if (keyCtrlPressed) { + glm::dvec2 mousePositionDelta = + _localRotationState.previousPosition - mousePosition; + _localRotationState.velocity.set( + mousePositionDelta * _sensitivity, + deltaTime + ); + + _globalRotationState.previousPosition = mousePosition; + _globalRotationState.velocity.decelerate(deltaTime); + } + else { + glm::dvec2 mousePositionDelta = + _globalRotationState.previousPosition - mousePosition; + _globalRotationState.velocity.set( + mousePositionDelta * _sensitivity, + deltaTime + ); + + _localRotationState.previousPosition = mousePosition; + _localRotationState.velocity.decelerate(deltaTime); + } + } + else { // !button1Pressed + _localRotationState.previousPosition = mousePosition; + _localRotationState.velocity.decelerate(deltaTime); + + _globalRotationState.previousPosition = mousePosition; + _globalRotationState.velocity.decelerate(deltaTime); + } + if (button2Pressed || (keyAltPressed && button1Pressed)) { + glm::dvec2 mousePositionDelta = + _truckMovementState.previousPosition - mousePosition; + _truckMovementState.velocity.set( + mousePositionDelta * _sensitivity, + deltaTime + ); + } + else { // !button2Pressed + _truckMovementState.previousPosition = mousePosition; + _truckMovementState.velocity.decelerate(deltaTime); + } + if (button3Pressed || (keyShiftPressed && button1Pressed)) { + if (keyCtrlPressed) { + glm::dvec2 mousePositionDelta = + _localRollState.previousPosition - mousePosition; + _localRollState.velocity.set( + mousePositionDelta * _sensitivity, + deltaTime + ); + + _globalRollState.previousPosition = mousePosition; + _globalRollState.velocity.decelerate(deltaTime); + } + else { + glm::dvec2 mousePositionDelta = + _globalRollState.previousPosition - mousePosition; + _globalRollState.velocity.set( + mousePositionDelta * _sensitivity, + deltaTime + ); + + _localRollState.previousPosition = mousePosition; + _localRollState.velocity.decelerate(deltaTime); + } + } + else { // !button3Pressed + _globalRollState.previousPosition = mousePosition; + _globalRollState.velocity.decelerate(deltaTime); + + _localRollState.previousPosition = mousePosition; + _localRollState.velocity.decelerate(deltaTime); + } +} + +} // namespace openspace::interaction diff --git a/src/interaction/mousestate.cpp b/src/interaction/mousestate.cpp deleted file mode 100644 index 9325eaf650..0000000000 --- a/src/interaction/mousestate.cpp +++ /dev/null @@ -1,179 +0,0 @@ -/***************************************************************************************** - * * - * OpenSpace * - * * - * Copyright (c) 2014-2018 * - * * - * 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::interaction { - -MouseState::MouseState(double scaleFactor) - : previousPosition(0.0, 0.0) - , velocity(scaleFactor, 1) -{} - -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) | - inputState.isKeyPressed(Key::RightControl); - bool keyShiftPressed = inputState.isKeyPressed(Key::LeftShift) | - inputState.isKeyPressed(Key::RightShift); - bool keyAltPressed = inputState.isKeyPressed(Key::LeftAlt) | - inputState.isKeyPressed(Key::RightAlt); - - // Update the mouse states - if (button1Pressed && !keyShiftPressed && !keyAltPressed) { - 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 || (keyAltPressed && button1Pressed)) { - 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 openspace::interaction diff --git a/src/interaction/navigationhandler.cpp b/src/interaction/navigationhandler.cpp index fb9631345a..2fff65ded4 100644 --- a/src/interaction/navigationhandler.cpp +++ b/src/interaction/navigationhandler.cpp @@ -153,8 +153,8 @@ void NavigationHandler::updateCamera(double deltaTime) { _keyframeNavigator->updateCamera(*_camera); } else { - _orbitalNavigator->updateMouseStatesFromInput(*_inputState, deltaTime); - _orbitalNavigator->updateCameraStateFromMouseStates(*_camera, deltaTime); + _orbitalNavigator->updateStatesFromInput(*_inputState, deltaTime); + _orbitalNavigator->updateCameraStateFromStates(*_camera, deltaTime); } _camera->setFocusPositionVec3(focusNode()->worldPosition()); } @@ -201,6 +201,10 @@ void NavigationHandler::keyboardCallback(Key key, KeyModifier modifier, KeyActio _inputState->keyboardCallback(key, modifier, action); } +void NavigationHandler::setJoystickInputStates(JoystickInputStates& states) { + _inputState->setJoystickInputStates(states); +} + void NavigationHandler::setCameraStateFromDictionary(const ghoul::Dictionary& cameraDict) { bool readSuccessful = true; @@ -226,7 +230,7 @@ void NavigationHandler::setCameraStateFromDictionary(const ghoul::Dictionary& ca cameraRotation.x, cameraRotation.y, cameraRotation.z, cameraRotation.w)); } -ghoul::Dictionary NavigationHandler::getCameraStateDictionary() { +ghoul::Dictionary NavigationHandler::cameraStateDictionary() { glm::dvec3 cameraPosition; glm::dquat quat; glm::dvec4 cameraRotation; @@ -248,7 +252,7 @@ void NavigationHandler::saveCameraStateToFile(const std::string& filepath) { std::string fullpath = absPath(filepath); LINFO(fmt::format("Saving camera position: {}", filepath)); - ghoul::Dictionary cameraDict = getCameraStateDictionary(); + ghoul::Dictionary cameraDict = cameraStateDictionary(); // 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. @@ -293,6 +297,52 @@ void NavigationHandler::restoreCameraStateFromFile(const std::string& filepath) } } +void NavigationHandler::setJoystickAxisMapping(int axis, JoystickCameraStates::AxisType mapping, + JoystickCameraStates::AxisInvert shouldInvert, + JoystickCameraStates::AxisNormalize shouldNormalize) +{ + _orbitalNavigator->joystickStates().setAxisMapping( + axis, + mapping, + shouldInvert, + shouldNormalize + ); +} + +JoystickCameraStates::AxisInformation NavigationHandler::joystickAxisMapping(int axis) const { + return _orbitalNavigator->joystickStates().axisMapping(axis); +} + +void NavigationHandler::setJoystickAxisDeadzone(int axis, float deadzone) { + _orbitalNavigator->joystickStates().setDeadzone(axis, deadzone); +} + +float NavigationHandler::joystickAxisDeadzone(int axis) const { + return _orbitalNavigator->joystickStates().deadzone(axis); +} + +void NavigationHandler::bindJoystickButtonCommand(int button, std::string command, + JoystickAction action, + JoystickCameraStates::ButtonCommandRemote remote) +{ + _orbitalNavigator->joystickStates().bindButtonCommand( + button, + std::move(command), + action, + remote + ); +} + +void NavigationHandler::clearJoystickButtonCommand(int button) { + _orbitalNavigator->joystickStates().clearButtonCommand(button); +} + +std::vector NavigationHandler::joystickButtonCommand(int button) const { + return _orbitalNavigator->joystickStates().buttonCommand(button); +} + + + scripting::LuaLibrary NavigationHandler::luaLibrary() { return { "navigation", @@ -324,6 +374,62 @@ scripting::LuaLibrary NavigationHandler::luaLibrary() { {}, "void", "Reset the camera direction to point at the focus node" + }, + { + "bindJoystickAxis", + &luascriptfunctions::bindJoystickAxis, + {}, + "int, axisType [, isInverted, isNormalized]", + "Binds the axis identified by the first argument to be used as the type " + "identified by the second argument. If 'isInverted' is 'true', the axis " + "value is inverted, if 'isNormalized' is true the axis value is " + "normalized from [-1, 1] to [0,1]." + }, + { + "joystickAxis", + &luascriptfunctions::joystickAxis, + {}, + "int", + "Returns the joystick axis information for the passed axis. The " + "information that is returned is the current axis binding as a string, " + "whether the values are inverted as bool, and whether the value are " + "normalized as a bool" + }, + { + "setAxisDeadZone", + &luascriptfunctions::setJoystickAxisDeadzone, + {}, + "int, float", + "Sets the deadzone for a particular joystick axis which means that any " + "input less than this value is completely ignored." + }, + { + "bindJoystickButton", + &luascriptfunctions::bindJoystickButton, + {}, + "int, string [, string, bool]", + "Binds a Lua script to be executed when the joystick button identified " + "by the first argument is triggered. The third argument determines when " + "the script should be executed, this defaults to 'pressed', which means " + "that the script is run when the user presses the button. The last " + "argument determines whether the command is going to be executable " + "locally or remotely. The latter being the default." + }, + { + "clearJoystickButotn", + &luascriptfunctions::clearJoystickButton, + {}, + "int", + "Removes all commands that are currently bound to the button identified " + "by the first argument" + }, + { + "joystickButton", + &luascriptfunctions::joystickButton, + {}, + "int", + "Returns the script that is currently bound to be executed when the " + "provided button is pressed" } } }; diff --git a/src/interaction/navigationhandler_lua.inl b/src/interaction/navigationhandler_lua.inl index 8ad65f406e..5a640be04e 100644 --- a/src/interaction/navigationhandler_lua.inl +++ b/src/interaction/navigationhandler_lua.inl @@ -22,6 +22,9 @@ * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * ****************************************************************************************/ +#include +#include + namespace openspace::luascriptfunctions { int restoreCameraStateFromFile(lua_State* L) { @@ -88,4 +91,130 @@ int resetCameraDirection(lua_State* L) { return 0; } +int bindJoystickAxis(lua_State* L) { + int n = ghoul::lua::checkArgumentsAndThrow(L, { 2, 4 }, "lua::bindJoystickAxis"); + + int axis = static_cast(lua_tonumber(L, 1)); + std::string axisType = lua_tostring(L, 2); + + bool shouldInvert = false; + if (n > 2) { + shouldInvert = lua_toboolean(L, 3); + } + + bool shouldNormalize = false; + if (n > 3) { + shouldNormalize = lua_toboolean(L, 4); + } + + OsEng.navigationHandler().setJoystickAxisMapping( + axis, + ghoul::from_string(axisType), + interaction::JoystickCameraStates::AxisInvert(shouldInvert), + interaction::JoystickCameraStates::AxisNormalize(shouldNormalize) + ); + + + return 0; +} + +int joystickAxis(lua_State* L) { + ghoul::lua::checkArgumentsAndThrow(L, 1, "lua::joystickAxis"); + + int axis = static_cast(lua_tonumber(L, 1)); + + using AI = interaction::JoystickCameraStates::AxisInformation; + AI info = OsEng.navigationHandler().joystickAxisMapping(axis); + + lua_settop(L, 0); + lua_pushstring(L, std::to_string(info.type).c_str()); + lua_pushboolean(L, info.invert); + lua_pushboolean(L, info.normalize); + + return 3; +} + +int setJoystickAxisDeadzone(lua_State* L) { + ghoul::lua::checkArgumentsAndThrow(L, 2, "lua::setJoystickAxisDeadzone"); + + int axis = static_cast(lua_tointeger(L, 1)); + float deadzone = static_cast(lua_tonumber(L, 2)); + + OsEng.navigationHandler().setJoystickAxisDeadzone(axis, deadzone); + + return 0; +} + +int joystickAxisDeadzone(lua_State* L) { + ghoul::lua::checkArgumentsAndThrow(L, 1, "lua::setJoystickAxisDeadzone"); + + int axis = static_cast(lua_tointeger(L, 1)); + + float deadzone = OsEng.navigationHandler().joystickAxisDeadzone(axis); + + lua_pushnumber(L, deadzone); + return 1; +} + +int bindJoystickButton(lua_State* L) { + int n = ghoul::lua::checkArgumentsAndThrow(L, { 2, 4 }, "lua::bindJoystickButton"); + + int button = static_cast(lua_tonumber(L, 1)); + std::string command = lua_tostring(L, 2); + + interaction::JoystickAction action = interaction::JoystickAction::Press; + if (n >= 3) { + action = ghoul::from_string(lua_tostring(L, 3)); + } + + bool isRemote = true; + if (n == 4) { + isRemote = lua_toboolean(L, 4); + } + + OsEng.navigationHandler().bindJoystickButtonCommand( + button, + std::move(command), + action, + interaction::JoystickCameraStates::ButtonCommandRemote(isRemote) + ); + + lua_settop(L, 0); + return 0; +} + +int clearJoystickButton(lua_State* L) { + int n = ghoul::lua::checkArgumentsAndThrow(L, 1, "lua::bindJoystickButton"); + + int button = static_cast(lua_tonumber(L, 1)); + + OsEng.navigationHandler().clearJoystickButtonCommand(button); + + lua_settop(L, 0); + return 0; +} + +int joystickButton(lua_State* L) { + int n = ghoul::lua::checkArgumentsAndThrow(L, 1, "lua::bindJoystickButton"); + + int button = static_cast(lua_tonumber(L, 1)); + + std::vector cmds = OsEng.navigationHandler().joystickButtonCommand( + button + ); + + std::string cmd = std::accumulate( + cmds.begin(), + cmds.end(), + std::string(), + [](std::string lhs, std::string rhs) { + return lhs + ";" + rhs; + } + ); + + lua_settop(L, 0); + lua_pushstring(L, cmd.c_str()); + return 1; +} + } // namespace openspace::luascriptfunctions diff --git a/src/interaction/orbitalnavigator.cpp b/src/interaction/orbitalnavigator.cpp index 29de597abe..b0e1fd439b 100644 --- a/src/interaction/orbitalnavigator.cpp +++ b/src/interaction/orbitalnavigator.cpp @@ -58,11 +58,18 @@ namespace { "disabled, the camera will zoom in or out forever." }; - static const openspace::properties::Property::PropertyInfo SensitivityInfo = { - "Sensitivity", - "Sensitivity", - "Determines the sensitivity of the camera motion. The lower the sensitivity is " - "the less impact a mouse mothion will have." + static const openspace::properties::Property::PropertyInfo MouseSensitivityInfo = { + "MouseSensitivity", + "Mouse Sensitivity", + "Determines the sensitivity of the camera motion thorugh the mouse. The lower " + "the sensitivity is the less impact a mouse motion will have." + }; + + static const openspace::properties::Property::PropertyInfo JoystickSensitivityInfo = { + "JoystickSensitivity", + "Joystick Sensitivity", + "Determines the sensitivity of the camera motion thorugh a joystick. The lower " + "the sensitivity is the less impact a joystick motion will have." }; static const openspace::properties::Property::PropertyInfo FrictionInfo = { @@ -105,8 +112,10 @@ OrbitalNavigator::OrbitalNavigator() : properties::PropertyOwner({ "OrbitalNavigator" }) , _followFocusNodeRotationDistance(FollowFocusNodeInfo, 5.0f, 0.0f, 20.f) , _minimumAllowedDistance(MinimumDistanceInfo, 10.0f, 0.0f, 10000.f) - , _sensitivity(SensitivityInfo, 15.0f, 1.0f, 50.f) - , _mouseStates(_sensitivity * pow(10.0, -4), 1 / (_friction.friction + 0.0000001)) + , _mouseSensitivity(MouseSensitivityInfo, 15.0f, 1.0f, 50.f) + , _joystickSensitivity(JoystickSensitivityInfo, 10.0f, 1.0f, 50.f) + , _mouseStates(_mouseSensitivity * 0.0001, 1 / (_friction.friction + 0.0000001)) + , _joystickStates(_joystickSensitivity * 0.1, 1 / (_friction.friction + 0.0000001)) { auto smoothStep = [](double t) { @@ -138,37 +147,47 @@ OrbitalNavigator::OrbitalNavigator() // Define callback functions for changed properties _friction.roll.onChange([&]() { _mouseStates.setRotationalFriction(_friction.roll); + _joystickStates.setRotationalFriction(_friction.roll); }); _friction.rotational.onChange([&]() { _mouseStates.setHorizontalFriction(_friction.rotational); + _joystickStates.setHorizontalFriction(_friction.rotational); }); _friction.zoom.onChange([&]() { _mouseStates.setVerticalFriction(_friction.zoom); + _joystickStates.setVerticalFriction(_friction.zoom); }); _friction.friction.onChange([&]() { _mouseStates.setVelocityScaleFactor(1 / (_friction.friction + 0.0000001)); + _joystickStates.setVelocityScaleFactor(1 / (_friction.friction + 0.0000001)); }); - _sensitivity.onChange([&]() { - _mouseStates.setSensitivity(_sensitivity * pow(10.0,-4)); + _mouseSensitivity.onChange([&]() { + _mouseStates.setSensitivity(_mouseSensitivity * pow(10.0, -4)); }); + _joystickSensitivity.onChange([&]() { + _joystickStates.setSensitivity(_joystickSensitivity * pow(10.0, -4)); + }); + addPropertySubOwner(_friction); addProperty(_followFocusNodeRotationDistance); addProperty(_minimumAllowedDistance); - addProperty(_sensitivity); + addProperty(_mouseSensitivity); + addProperty(_joystickSensitivity); } OrbitalNavigator::~OrbitalNavigator() {} -void OrbitalNavigator::updateMouseStatesFromInput(const InputState& inputState, +void OrbitalNavigator::updateStatesFromInput(const InputState& inputState, double deltaTime) { - _mouseStates.updateMouseStatesFromInput(inputState, deltaTime); + _mouseStates.updateStateFromInput(inputState, deltaTime); + _joystickStates.updateStateFromInput(inputState, deltaTime); } -void OrbitalNavigator::updateCameraStateFromMouseStates(Camera& camera, double deltaTime){ +void OrbitalNavigator::updateCameraStateFromStates(Camera& camera, double deltaTime){ if (_focusNode) { // Read the current state of the camera glm::dvec3 camPos = camera.positionVec3(); @@ -347,23 +366,31 @@ OrbitalNavigator::CameraRotationDecomposition glm::dquat OrbitalNavigator::roll(double deltaTime, const glm::dquat& localCameraRotation) const { - glm::dquat rollQuat = glm::angleAxis( - _mouseStates.localRollMouseVelocity().x * deltaTime, + glm::dquat mouseRollQuat = glm::angleAxis( + _mouseStates.localRollVelocity().x * deltaTime + + _joystickStates.localRollVelocity().x * deltaTime, glm::dvec3(0.0, 0.0, 1.0) ); - return localCameraRotation * rollQuat; + return localCameraRotation * mouseRollQuat; } glm::dquat OrbitalNavigator::rotateLocally(double deltaTime, const glm::dquat& localCameraRotation) const { - glm::dvec3 eulerAngles( - _mouseStates.localRotationMouseVelocity().y, - _mouseStates.localRotationMouseVelocity().x, + glm::dquat mouseRotationDiff = glm::dquat(glm::dvec3( + _mouseStates.localRotationVelocity().y, + _mouseStates.localRotationVelocity().x, 0.0 - ); - glm::dquat rotationDiff = glm::dquat(eulerAngles * deltaTime); - return localCameraRotation * rotationDiff; + ) * deltaTime); + + glm::dquat joystickRotationDiff = glm::dquat(glm::dvec3( + _joystickStates.localRotationVelocity().y, + _joystickStates.localRotationVelocity().x, + 0.0 + ) * deltaTime); + + + return localCameraRotation * joystickRotationDiff * mouseRotationDiff; } glm::dquat OrbitalNavigator::interpolateLocalRotation(double deltaTime, @@ -425,16 +452,21 @@ glm::dvec3 OrbitalNavigator::translateHorizontally(double deltaTime, 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); + glm::dquat mouseRotationDiffCamSpace = glm::dquat(glm::dvec3( + -_mouseStates.globalRotationVelocity().y * deltaTime, + -_mouseStates.globalRotationVelocity().x * deltaTime, + 0) * speedScale); + + glm::dquat joystickRotationDiffCamSpace = glm::dquat(glm::dvec3( + -_joystickStates.globalRotationVelocity().y * deltaTime, + -_joystickStates.globalRotationVelocity().x * deltaTime, + 0) * speedScale); // Transform to world space glm::dquat rotationDiffWorldSpace = globalCameraRotation * - rotationDiffCamSpace * + joystickRotationDiffCamSpace * + mouseRotationDiffCamSpace * glm::inverse(globalCameraRotation); // Rotate and find the difference vector @@ -501,8 +533,12 @@ glm::dvec3 OrbitalNavigator::translateVertically( glm::dmat3(modelTransform) * centerToActualSurfaceModelSpace; glm::dvec3 actualSurfaceToCamera = posDiff - centerToActualSurface; + const double totalVelocity = + _joystickStates.truckMovementVelocity().y + + _mouseStates.truckMovementVelocity().y; + return cameraPosition - - actualSurfaceToCamera * _mouseStates.truckMovementMouseVelocity().y * deltaTime; + actualSurfaceToCamera * totalVelocity * deltaTime; } glm::dquat OrbitalNavigator::rotateHorizontally( @@ -518,12 +554,12 @@ glm::dquat OrbitalNavigator::rotateHorizontally( glm::dvec3 directionFromSurfaceToCamera = glm::normalize(glm::dmat3(modelTransform) * directionFromSurfaceToCameraModelSpace); - glm::dquat cameraRollRotation = - glm::angleAxis( - _mouseStates.globalRollMouseVelocity().x * - deltaTime, directionFromSurfaceToCamera - ); - return cameraRollRotation * globalCameraRotation; + glm::dquat mouseCameraRollRotation = glm::angleAxis( + _mouseStates.globalRollVelocity().x * deltaTime + + _joystickStates.globalRollVelocity().x * deltaTime, + directionFromSurfaceToCamera + ); + return mouseCameraRollRotation * globalCameraRotation; } glm::dvec3 OrbitalNavigator::pushToSurface( @@ -597,4 +633,9 @@ SurfacePositionHandle OrbitalNavigator::calculateSurfacePositionHandle( return posHandle; } +JoystickCameraStates& OrbitalNavigator::joystickStates() { + return _joystickStates; +} + + } // namespace openspace::interaction diff --git a/src/rendering/luaconsole.cpp b/src/rendering/luaconsole.cpp new file mode 100644 index 0000000000..bb04359b78 --- /dev/null +++ b/src/rendering/luaconsole.cpp @@ -0,0 +1,916 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2018 * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this * + * software and associated documentation files (the "Software"), to deal in the Software * + * without restriction, including without limitation the rights to use, copy, modify, * + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * + * permit persons to whom the Software is furnished to do so, subject to the following * + * conditions: * + * * + * The above copyright notice and this permission notice shall be included in all copies * + * or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + ****************************************************************************************/ + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace { + const char* HistoryFile = "ConsoleHistory"; + + const int NoAutoComplete = -1; + + const int MaximumHistoryLength = 1000; + + // A high number is chosen since we didn't have a version number before + // any small number might also be equal to the console history length + + const uint64_t CurrentVersion = 0xFEEE'FEEE'0000'0001; + + const openspace::Key CommandInputButton = openspace::Key::GraveAccent; + + const char* FontName = "Console"; + const float EntryFontSize = 14.0f; + const float HistoryFontSize = 11.0f; + + // Additional space between the entry text and the history (in pixels) + const float SeparatorSpace = 30.f; + + // Determines at which speed the console opens. + const float ConsoleOpenSpeed = 2.5; + + // The number of characters to display after the cursor + // when horizontal scrolling is required. + const int NVisibleCharsAfterCursor = 5; + + const static openspace::properties::Property::PropertyInfo VisibleInfo = { + "IsVisible", + "Is Visible", + "Determines whether the Lua console is shown on the screen or not. Toggling it " + "will fade the console in and out." + }; + + const static openspace::properties::Property::PropertyInfo RemoveScriptingInfo = { + "RemoteScripting", + "Remote scripting", + "Determines whether the entered commands will only be executed locally (if this " + "is disabled), or whether they will be send to connected remove instances." + }; + + const static openspace::properties::Property::PropertyInfo BackgroundColorInfo = { + "BackgroundColor", + "Background Color", + "Sets the background color of the console." + }; + + const static openspace::properties::Property::PropertyInfo HighlightColorInfo = { + "HighlightColor", + "Highlight Color", + "Sets the color of the lines below the console." + }; + + const static openspace::properties::Property::PropertyInfo SeparatorColorInfo = { + "SeparatorColor", + "Separator Color", + "Sets the color of the separator between the history part and the entry part of " + "the console." + }; + + const static openspace::properties::Property::PropertyInfo EntryTextColorInfo = { + "EntryTextColor", + "Entry Text Color", + "Sets the text color of the entry area of the console." + }; + + const static openspace::properties::Property::PropertyInfo HistoryTextColorInfo = { + "HistoryTextColor", + "History Text Color", + "Sets the text color of the history area of the console." + }; + + const static openspace::properties::Property::PropertyInfo HistoryLengthInfo = { + "HistoryLength", + "History Length", + "Determines the length of the history in number of lines." + }; +} // namespace + +namespace openspace { + +LuaConsole::LuaConsole() + : properties::PropertyOwner({ "LuaConsole" }) + , _isVisible(VisibleInfo, false) + , _remoteScripting(RemoveScriptingInfo, false) + , _backgroundColor( + BackgroundColorInfo, + glm::vec4(21.f / 255.f, 23.f / 255.f, 28.f / 255.f, 0.8f), + glm::vec4(0.f), + glm::vec4(1.f) + ) + , _highlightColor( + HighlightColorInfo, + glm::vec4(1.f, 1.f, 1.f, 0.f), + glm::vec4(0.f), + glm::vec4(1.f) + ) + , _separatorColor( + SeparatorColorInfo, + glm::vec4(0.4f, 0.4f, 0.4f, 0.f), + glm::vec4(0.f), + glm::vec4(1.f) + ) + , _entryTextColor( + EntryTextColorInfo, + glm::vec4(1.f, 1.f, 1.f, 1.f), + glm::vec4(0.f), + glm::vec4(1.f) + ) + , _historyTextColor( + HistoryTextColorInfo, + glm::vec4(1.0f, 1.0f, 1.0f, 0.65f), + glm::vec4(0.f), + glm::vec4(1.f) + ) + , _historyLength(HistoryLengthInfo, 13, 0, 100) + , _inputPosition(0) + , _activeCommand(0) + , _autoCompleteInfo({NoAutoComplete, false, ""}) + , _currentHeight(0.f) + , _targetHeight(0.f) + , _fullHeight(0.f) +{ + addProperty(_isVisible); + addProperty(_remoteScripting); + addProperty(_historyLength); + + _backgroundColor.setViewOption(properties::Property::ViewOptions::Color); + addProperty(_backgroundColor); + _highlightColor.setViewOption(properties::Property::ViewOptions::Color); + addProperty(_highlightColor); + _separatorColor.setViewOption(properties::Property::ViewOptions::Color); + addProperty(_separatorColor); + _entryTextColor.setViewOption(properties::Property::ViewOptions::Color); + addProperty(_entryTextColor); + _historyTextColor.setViewOption(properties::Property::ViewOptions::Color); + addProperty(_historyTextColor); +} + +void LuaConsole::initialize() { + const std::string filename = FileSys.cacheManager()->cachedFilename( + HistoryFile, + "", + ghoul::filesystem::CacheManager::Persistent::Yes + ); + + if (FileSys.fileExists(filename)) { + std::ifstream file(filename, std::ios::binary | std::ios::in); + + if (file.good()) { + // Read the number of commands from the history + uint64_t version; + file.read(reinterpret_cast(&version), sizeof(uint64_t)); + + if (version != CurrentVersion) { + LWARNINGC( + "LuaConsole", + fmt::format("Outdated console history version: {}", version) + ); + } + else { + int64_t nCommands; + file.read(reinterpret_cast(&nCommands), sizeof(int64_t)); + + for (int64_t i = 0; i < nCommands; ++i) { + int64_t length; + file.read(reinterpret_cast(&length), sizeof(int64_t)); + + std::vector tmp(length); + file.read(tmp.data(), length); + _commandsHistory.emplace_back(std::string(tmp.begin(), tmp.end())); + } + } + } + } + + _commands = _commandsHistory; + _commands.push_back(""); + _activeCommand = _commands.size() - 1; + + _program = ghoul::opengl::ProgramObject::Build( + "Console", + absPath("${SHADERS}/luaconsole.vert"), + absPath("${SHADERS}/luaconsole.frag") + ); + + _uniformCache.res = _program->uniformLocation("res"); + _uniformCache.color = _program->uniformLocation("color"); + _uniformCache.height = _program->uniformLocation("height"); + _uniformCache.ortho = _program->uniformLocation("ortho"); + + GLfloat data[] = { + 0.f, 0.f, + 1.f, 1.f, + 0.f, 1.f, + + 0.f, 0.f, + 1.f, 0.f, + 1.f, 1.f + }; + + glGenVertexArrays(1, &_vao); + glBindVertexArray(_vao); + glGenBuffers(1, &_vbo); + glBindBuffer(GL_ARRAY_BUFFER, _vbo); + glBufferData(GL_ARRAY_BUFFER, sizeof(data), data, GL_STATIC_DRAW); + + glEnableVertexAttribArray(0); + glVertexAttribPointer( + 0, + 2, + GL_FLOAT, + GL_FALSE, + 2 * sizeof(GLfloat), + nullptr + ); + + glBindVertexArray(0); + + _font = OsEng.fontManager().font( + FontName, + EntryFontSize, + ghoul::fontrendering::FontManager::Outline::No + ); + + _historyFont = OsEng.fontManager().font( + FontName, + HistoryFontSize, + ghoul::fontrendering::FontManager::Outline::No + ); + + OsEng.parallelPeer().connectionEvent()->subscribe( + "luaConsole", + "statusChanged", + [this]() { + ParallelConnection::Status status = OsEng.parallelPeer().status(); + parallelConnectionChanged(status); + } + ); +} + +void LuaConsole::deinitialize() { + const std::string filename = FileSys.cacheManager()->cachedFilename( + HistoryFile, + "", + ghoul::filesystem::CacheManager::Persistent::Yes + ); + + // We want to limit the command history to a realistic value, so that it doesn't + // grow without bounds + if (_commandsHistory.size() > MaximumHistoryLength) { + _commandsHistory = std::vector( + _commandsHistory.end() - MaximumHistoryLength, + _commandsHistory.end() + ); + } + + std::ofstream file(filename, std::ios::binary); + if (file.good()) { + uint64_t version = CurrentVersion; + file.write(reinterpret_cast(&version), sizeof(uint64_t)); + + int64_t nCommands = _commandsHistory.size(); + file.write(reinterpret_cast(&nCommands), sizeof(int64_t)); + + for (const std::string& s : _commandsHistory) { + int64_t length = s.length(); + file.write(reinterpret_cast(&length), sizeof(int64_t)); + // We don't write the \0 at the end on purpose + file.write(s.c_str(), length); + } + } + + _program = nullptr; + + OsEng.parallelPeer().connectionEvent()->unsubscribe("luaConsole"); +} + +bool LuaConsole::keyboardCallback(Key key, KeyModifier modifier, KeyAction action) { + if (action != KeyAction::Press && action != KeyAction::Repeat) { + return false; + } + + if (key == CommandInputButton) { + // Button left of 1 and above TAB + // How to deal with different keyboard languages? ---abock + if (_isVisible) { + if (_remoteScripting) { + _remoteScripting = false; + } + else { + _isVisible = false; + _commands.back() = ""; + _inputPosition = 0; + } + } + else { + _isVisible = true; + if (OsEng.parallelPeer().status() == ParallelConnection::Status::Host) { + _remoteScripting = true; + } + } + + return true; + } + + if (!_isVisible) { + return false; + } + + if (key == Key::Escape) { + _isVisible = false; + return true; + } + + + const bool modifierControl = (modifier == KeyModifier::Control); + const bool modifierShift = (modifier == KeyModifier::Shift); + + // Paste from clipboard + if (modifierControl && (key == Key::V || key == Key::Y)) { + addToCommand(sanitizeInput(ghoul::clipboardText())); + return true; + } + + // Copy to clipboard + if (modifierControl && key == Key::C) { + ghoul::setClipboardText(_commands.at(_activeCommand)); + return true; + } + + // Cut to clipboard + if (modifierControl && key == Key::X) { + ghoul::setClipboardText(_commands.at(_activeCommand)); + _commands.at(_activeCommand).clear(); + _inputPosition = 0; + } + + // Cut part after cursor to clipboard ("Kill") + if (modifierControl && key == Key::K) { + auto here = _commands.at(_activeCommand).begin() + _inputPosition; + auto end = _commands.at(_activeCommand).end(); + ghoul::setClipboardText(std::string(here, end)); + _commands.at(_activeCommand).erase(here, end); + } + + // Go to the previous character + if (key == Key::Left || (modifierControl && key == Key::B)) { + if (_inputPosition > 0) { + --_inputPosition; + } + return true; + } + + // Go to the next character + if (key == Key::Right || (modifierControl && key == Key::F)) { + _inputPosition = std::min( + _inputPosition + 1, + _commands.at(_activeCommand).length() + ); + return true; + } + + // Go to previous command + if (key == Key::Up) { + if (_activeCommand > 0) { + --_activeCommand; + } + _inputPosition = _commands.at(_activeCommand).length(); + return true; + } + + // Go to next command (the last is empty) + if (key == Key::Down) { + if (_activeCommand < _commands.size() - 1) { + ++_activeCommand; + } + _inputPosition = _commands.at(_activeCommand).length(); + return true; + } + + // Remove character before _inputPosition + if (key == Key::BackSpace) { + if (_inputPosition > 0) { + _commands.at(_activeCommand).erase(_inputPosition - 1, 1); + --_inputPosition; + } + return true; + } + + // Remove character after _inputPosition + if (key == Key::Delete) { + if (_inputPosition <= _commands.at(_activeCommand).size()) { + _commands.at(_activeCommand).erase(_inputPosition, 1); + } + return true; + } + + // Go to the beginning of command string + if (key == Key::Home || (modifierControl && key == Key::A)) { + _inputPosition = 0; + return true; + } + + // Go to the end of command string + if (key == Key::End || (modifierControl && key == Key::E)) { + _inputPosition = _commands.at(_activeCommand).size(); + return true; + } + + if (key == Key::Enter || key == Key::KeypadEnter) { + std::string cmd = _commands.at(_activeCommand); + if (cmd != "") { + using RemoteScripting = scripting::ScriptEngine::RemoteScripting; + OsEng.scriptEngine().queueScript( + cmd, + _remoteScripting ? RemoteScripting::Yes : RemoteScripting::No + ); + + // Only add the current command to the history if it hasn't been + // executed before. We don't want two of the same commands in a row + if (_commandsHistory.empty() || (cmd != _commandsHistory.back())) { + _commandsHistory.push_back(_commands.at(_activeCommand)); + } + } + + // Some clean up after the execution of the command + _commands = _commandsHistory; + _commands.push_back(""); + _activeCommand = _commands.size() - 1; + _inputPosition = 0; + return true; + } + + if (key == Key::Tab) { + // We get a list of all the available commands and initially find the first + // command that starts with how much we typed sofar. We store the index so + // that in subsequent "tab" presses, we will discard previous commands. This + // implements the 'hop-over' behavior. As soon as another key is pressed, + // everything is set back to normal + + // If the shift key is pressed, we decrement the current index so that we will + // find the value before the one that was previously found + if (_autoCompleteInfo.lastIndex != NoAutoComplete && modifierShift) { + _autoCompleteInfo.lastIndex -= 2; + } + std::vector allCommands = OsEng.scriptEngine().allLuaFunctions(); + std::sort(allCommands.begin(), allCommands.end()); + + std::string currentCommand = _commands.at(_activeCommand); + + // Check if it is the first time the tab has been pressed. If so, we need to + // store the already entered command so that we can later start the search + // from there. We will overwrite the 'currentCommand' thus making the storage + // necessary + if (!_autoCompleteInfo.hasInitialValue) { + _autoCompleteInfo.initialValue = currentCommand; + _autoCompleteInfo.hasInitialValue = true; + } + + for (int i = 0; i < static_cast(allCommands.size()); ++i) { + const std::string& command = allCommands[i]; + + // Check if the command has enough length (we don't want crashes here) + // Then check if the iterator-command's start is equal to what we want + // then check if we need to skip the first found values as the user has + // pressed TAB repeatedly + size_t fullLength = _autoCompleteInfo.initialValue.length(); + bool correctLength = command.length() >= fullLength; + + std::string commandLowerCase; + std::transform( + command.begin(), command.end(), + std::back_inserter(commandLowerCase), + [](char v) { return static_cast(tolower(v)); } + ); + + std::string initialValueLowerCase; + std::transform( + _autoCompleteInfo.initialValue.begin(), + _autoCompleteInfo.initialValue.end(), + std::back_inserter(initialValueLowerCase), + [](char v) { return static_cast(tolower(v)); } + ); + + bool correctCommand = + commandLowerCase.substr(0, fullLength) == initialValueLowerCase; + + if (correctLength && correctCommand && (i > _autoCompleteInfo.lastIndex)) { + // We found our index, so store it + _autoCompleteInfo.lastIndex = i; + + // We only want to auto-complete until the next separator "." + size_t pos = command.find('.', fullLength); + if (pos == std::string::npos) { + // If we don't find a separator, we autocomplete until the end + // Set the found command as active command + _commands.at(_activeCommand) = command + "();"; + // Set the cursor position to be between the brackets + _inputPosition = _commands.at(_activeCommand).size() - 2; + } + else { + // If we find a separator, we autocomplete until and including the + // separator unless the autocompletion would be the same that we + // already have (the case if there are multiple commands in the + // same group + std::string subCommand = command.substr(0, pos + 1); + if (subCommand == _commands.at(_activeCommand)) { + continue; + } + else { + _commands.at(_activeCommand) = command.substr(0, pos + 1); + _inputPosition = _commands.at(_activeCommand).length(); + // We only want to remove the autocomplete info if we just + // entered the 'default' openspace namespace + if (command.substr(0, pos + 1) == "openspace.") { + _autoCompleteInfo = { NoAutoComplete, false, "" }; + } + } + } + + break; + } + } + return true; + } + else { + // If any other key is pressed, we want to remove our previous findings + // The special case for Shift is necessary as we want to allow Shift+TAB + if (!modifierShift) { + _autoCompleteInfo = { NoAutoComplete, false, "" }; + } + } + + // We want to ignore the function keys as they don't translate to text anyway + if (key >= Key::F1 && key <= Key::F25) { + return false; + } + + // Do not consume modifier keys + switch (key) { + case Key::LeftShift: + case Key::RightShift: + case Key::LeftAlt: + case Key::RightAlt: + case Key::LeftControl: + case Key::RightControl: + return false; + default: + return true; + } +} + +void LuaConsole::charCallback(unsigned int codepoint, + [[maybe_unused]] KeyModifier modifier) +{ + if (!_isVisible) { + return; + } + + if (codepoint == static_cast(CommandInputButton)) { + return; + } + +#ifndef WIN32 + const bool modifierControl = (modifier == KeyModifier::Control); + + const int codepoint_C = 99; + const int codepoint_V = 118; + if (modifierControl && (codepoint == codepoint_C || codepoint == codepoint_V)) { + return; + } +#endif + + // Disallow all non ASCII characters for now + if (codepoint > 0x7f) { + return; + } + + addToCommand(std::string(1, static_cast(codepoint))); +} + +void LuaConsole::update() { + // Compute the height by simulating _historyFont number of lines and checking + // what the bounding box for that text would be. + using namespace ghoul::fontrendering; + const size_t nLines = std::min( + static_cast(_historyLength), + _commandsHistory.size() + ); + const auto bbox = FontRenderer::defaultRenderer().boundingBox( + *_historyFont, + std::string(nLines, '\n').c_str() + ); + + // Update the full height and the target height. + // Add the height of the entry line and space for a separator. + _fullHeight = (bbox.boundingBox.y + EntryFontSize + SeparatorSpace); + _targetHeight = _isVisible ? _fullHeight : 0; + + // The first frame is going to be finished in approx 10 us, which causes a floating + // point overflow when computing dHeight + const double frametime = std::max( + OsEng.windowWrapper().deltaTime(), + 1e-4 + ); + + // Update the current height. + // The current height is the offset that is used to slide + // the console in from the top. + const glm::ivec2 res = OsEng.windowWrapper().currentWindowResolution(); + const glm::vec2 dpiScaling = OsEng.windowWrapper().dpiScaling(); + const double dHeight = (_targetHeight - _currentHeight) * + std::pow(0.98, 1.0 / (ConsoleOpenSpeed / dpiScaling.y * frametime)); + + _currentHeight += static_cast(dHeight); + + _currentHeight = std::max(0.0f, _currentHeight); + _currentHeight = std::min(static_cast(res.y), _currentHeight); +} + +void LuaConsole::render() { + using namespace ghoul::fontrendering; + + // Don't render the console if it's collapsed. + if (_currentHeight < 1.0f) { + return; + } + + if (_program->isDirty()) { + _program->rebuildFromFile(); + + _uniformCache.res = _program->uniformLocation("res"); + _uniformCache.color = _program->uniformLocation("color"); + _uniformCache.height = _program->uniformLocation("height"); + _uniformCache.ortho = _program->uniformLocation("ortho"); + } + + const glm::vec2 dpiScaling = OsEng.windowWrapper().dpiScaling(); + const glm::ivec2 res = + glm::vec2(OsEng.windowWrapper().currentWindowResolution()) / dpiScaling; + + + // Render background + glDisable(GL_CULL_FACE); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDisable(GL_DEPTH_TEST); + + _program->activate(); + + _program->setUniform(_uniformCache.res, res); + _program->setUniform(_uniformCache.color, _backgroundColor); + _program->setUniform(_uniformCache.height, _currentHeight / res.y); + _program->setUniform( + _uniformCache.ortho, + glm::ortho( + 0.f, static_cast(res.x), 0.f, static_cast(res.y) + ) + ); + + // Draw the background color + glBindVertexArray(_vao); + glDrawArrays(GL_TRIANGLES, 0, 6); + + // Draw the highlight lines above and below the background + _program->setUniform(_uniformCache.color, _highlightColor); + glDrawArrays(GL_LINES, 1, 4); + + // Draw the separator between the current entry box and the history + _program->setUniform(_uniformCache.color, _separatorColor); + _program->setUniform( + _uniformCache.height, + _currentHeight / res.y - 2.5f * EntryFontSize / res.y + ); + glDrawArrays(GL_LINES, 1, 2); + + _program->deactivate(); + + glEnable(GL_CULL_FACE); + glEnable(GL_DEPTH_TEST); + + // Render text on top of the background + glm::vec2 inputLocation = glm::vec2( + EntryFontSize / 2.f, + res.y - _currentHeight + EntryFontSize + ); + + // Render the current command + std::string currentCommand = _commands.at(_activeCommand); + // We chop off the beginning and end of the string until it fits on the screen (with + // a margin) this should be replaced as soon as the mono-spaced fonts work properly. + // Right now, every third character is a bit wider than the others + + size_t nChoppedCharsBeginning = 0, nChoppedCharsEnd = 0; + + const size_t inputPositionFromEnd = currentCommand.size() - _inputPosition; + while (true) { + using namespace ghoul::fontrendering; + // Compute the current width of the string and console prefix. + const float currentWidth = FontRenderer::defaultRenderer().boundingBox( + *_font, + "> %s", + currentCommand.c_str() + ).boundingBox.x + inputLocation.x; + + // Compute the overflow in pixels + const float overflow = currentWidth - res.x * 0.995f; + if (overflow <= 0.f) { + break; + } + + // Since the overflow is positive, at least one character needs to be removed. + const size_t nCharsOverflow = static_cast(std::min( + std::max(1.f, overflow / _font->glyph('m')->width()), + static_cast(currentCommand.size()) + )); + + // Do not hide the cursor and `NVisibleCharsAfterCursor` characters in the end. + const size_t maxAdditionalCharsToChopEnd = std::max( + 0, + static_cast(inputPositionFromEnd) - + (NVisibleCharsAfterCursor + 1) - + static_cast(nChoppedCharsEnd) + ); + + // Do not hide the cursor in the beginning. + const size_t maxAdditionalCharsToChopBeginning = std::max( + 0, + static_cast(_inputPosition) - 1 - + static_cast(nChoppedCharsBeginning) + ); + + // Prioritize chopping in the end of the string. + const size_t nCharsToChopEnd = std::min( + nCharsOverflow, + maxAdditionalCharsToChopEnd + ); + const size_t nCharsToChopBeginning = std::min( + nCharsOverflow - nCharsToChopEnd, + maxAdditionalCharsToChopBeginning + ); + + nChoppedCharsBeginning += nCharsToChopBeginning; + nChoppedCharsEnd += nCharsToChopEnd; + + const size_t displayLength = + _commands.at(_activeCommand).size() - + nChoppedCharsBeginning - nChoppedCharsEnd; + + currentCommand = _commands.at(_activeCommand).substr( + nChoppedCharsBeginning, + displayLength + ); + } + + RenderFontCr( + *_font, + inputLocation, + _entryTextColor, + "> %s", + currentCommand.c_str() + ); + + // Just offset the ^ marker slightly for a nicer look + inputLocation.y += 3 * dpiScaling.y; + + // Render the ^ marker below the text to show where the current entry point is + RenderFont( + *_font, + inputLocation, + _entryTextColor, + (std::string(_inputPosition - nChoppedCharsBeginning + 2, ' ') + "^").c_str() + ); + + glm::vec2 historyInputLocation = glm::vec2( + HistoryFontSize / 2.f, + res.y - HistoryFontSize * 1.5f + _fullHeight - _currentHeight + ); + + // @CPP: Replace with array_view + std::vector commandSubset; + if (_commandsHistory.size() < static_cast(_historyLength)) { + commandSubset = _commandsHistory; + } + else { + commandSubset = std::vector( + _commandsHistory.end() - _historyLength, + _commandsHistory.end() + ); + } + + for (const std::string& cmd : commandSubset) { + RenderFontCr( + *_historyFont, + historyInputLocation, + _historyTextColor, + "%s", + cmd.c_str() + ); + } + + // Computes the location for right justified text on the same y height as the entry + auto locationForRightJustifiedText = [&](const std::string& text) { + using namespace ghoul::fontrendering; + + const glm::vec2 loc = glm::vec2( + EntryFontSize / 2.f, + res.y - _currentHeight + EntryFontSize + ); + + const auto bbox = FontRenderer::defaultRenderer().boundingBox( + *_font, text.c_str() + ); + return glm::vec2( + loc.x + res.x - bbox.boundingBox.x - 10.f, + loc.y + ); + }; + + if (_remoteScripting) { + const glm::vec4 red(1, 0, 0, 1); + + ParallelConnection::Status status = OsEng.parallelPeer().status(); + const int nClients = + status != ParallelConnection::Status::Disconnected ? + OsEng.parallelPeer().nConnections() - 1 : + 0; + + const std::string nClientsText = + nClients == 1 ? + "Broadcasting script to 1 client" : + "Broadcasting script to " + std::to_string(nClients) + " clients"; + + const glm::vec2 loc = locationForRightJustifiedText(nClientsText); + RenderFont(*_font, loc, red, nClientsText.c_str()); + } else if (OsEng.parallelPeer().isHost()) { + const glm::vec4 lightBlue(0.4, 0.4, 1, 1); + + const std::string localExecutionText = "Local script execution"; + const glm::vec2 loc = locationForRightJustifiedText(localExecutionText); + RenderFont(*_font, loc, lightBlue, localExecutionText.c_str()); + } +} + +float LuaConsole::currentHeight() const { + return _currentHeight; +} + +void LuaConsole::addToCommand(std::string c) { + const size_t length = c.length(); + _commands.at(_activeCommand).insert(_inputPosition, std::move(c)); + _inputPosition += length; +} + +std::string LuaConsole::sanitizeInput(std::string str) { + // Remove carriage returns. + str.erase(std::remove(str.begin(), str.end(), '\r'), str.end()); + + // Replace newlines with spaces. + const std::function replace = [](char c) { + return c == '\n' ? ' ' : c; + }; + std::transform(str.begin(), str.end(), str.begin(), replace); + + return str; +} + +void LuaConsole::parallelConnectionChanged(const ParallelConnection::Status& status) { + _remoteScripting = (status == ParallelConnection::Status::Host); +} + +} // namespace openspace diff --git a/src/rendering/renderengine.cpp b/src/rendering/renderengine.cpp index c6d1dc46ad..4b0254e367 100644 --- a/src/rendering/renderengine.cpp +++ b/src/rendering/renderengine.cpp @@ -31,15 +31,15 @@ #include #include #include -#include #include #include #include #include #include #include -#include #include +#include +#include #include #include #include