diff --git a/apps/OpenSpace/main.cpp b/apps/OpenSpace/main.cpp index 063057d6a5..b761225f75 100644 --- a/apps/OpenSpace/main.cpp +++ b/apps/OpenSpace/main.cpp @@ -369,27 +369,34 @@ void mainPreSyncFunc() { std::fill(state.axes.begin(), state.axes.end(), 0.f); std::fill(state.buttons.begin(), state.buttons.end(), JoystickAction::Idle); + + // Check axes and buttons + glfwGetJoystickAxes(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 + )); + } + glfwGetJoystickButtons(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 + )); + } } const float* axes = glfwGetJoystickAxes(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 = glfwGetJoystickButtons(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; } diff --git a/data/assets/examples/joystickProperty.asset b/data/assets/examples/joystickProperty.asset new file mode 100644 index 0000000000..a5aedceace --- /dev/null +++ b/data/assets/examples/joystickProperty.asset @@ -0,0 +1,90 @@ +-- Allowed values for the third parameter of bindJoystickAxis: +-- "None" +-- "Orbit X" +-- "Orbit Y" +-- "Zoom" -- both in and out +-- "Zoom In" +-- "Zoom Out" +-- "LocalRoll X" +-- "LocalRoll Y" +-- "GlobalRoll X" +-- "GlobalRoll Y" +-- "Pan X" +-- "Pan Y" +-- Fourth parameter determines whether the axis should be inverted +-- Fifth parameter determines whether the axis behaves like a joystick or a Trigger. +-- Allowed values are "JoystickLike" and "TriggerLike", the first one is the default +-- Sixth parameters determins if the axis should be "Sticky" or not. +-- The axis values can either go back to 0 when the joystick is released or it can +-- stay at the value it was before the joystick was released. +-- The latter is called a sticky axis, when the values don't go back to 0. +-- Seventh parameter is the sensitivity for the axis + + +-- Parameters for bindJoystickAxisProperty: +-- First - Name of the joystick that should be bound +-- Second - Which axis should be bound of this joystick +-- Third - The property uri +-- Fourth - (optional) The smallest value that you wnat to allow this property on the joystick +-- Fifth - (optional) The largest value that you wnat to allow this property on the joystick +-- Sixth - (optional) Determines whether the axis should be inverted +-- Seventh - (optional) Should this property change be sent to other connected remote sessions + +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, + LeftStickButton = 8, + RightStickButton = 9, + DPad = { + Up = 10, + Right = 11, + Down = 12, + Left = 13 + } +} + +local freezeValue = function(name, axis, min, max) + return [[ + local joystickType = openspace.navigation.joystickAxis(']] .. name .. "', " .. axis .. [[); + local isPropertyBound = true; + if joystickType == "None" then + isPropertyBound = false; + else + isPropertyBound = true; + end + + if isPropertyBound then + openspace.navigation.bindJoystickAxis(']] .. name .. "', " .. axis .. [[, "None"); + isPropertyBound = false; + else + openspace.navigation.bindJoystickAxisProperty(']] .. name .. "', " .. axis .. [[, 'Scene.Earth.Scale.Scale', ]] .. min ..", " .. max.. [[); + isPropertyBound = true; + end + ]] +end + +asset.onInitialize(function() + local controller = XBoxController; + local name = "Xbox Controller"; + + -- Bind Right trigger to Earth Scale + openspace.navigation.bindJoystickAxisProperty(name, controller.RightTrigger, "Scene.Earth.Scale.Scale", 0.1, 100); + + -- Bind 'A' button to freeze current value + openspace.navigation.bindJoystickButton( + name, + controller.A, + freezeValue(name, controller.RightTrigger, 0.1, 100), + "Freeze current scale for Earth. Or release it to the trigger again." + ) +end) diff --git a/include/openspace/interaction/joystickcamerastates.h b/include/openspace/interaction/joystickcamerastates.h index d0b2d74039..2c96b58a5c 100644 --- a/include/openspace/interaction/joystickcamerastates.h +++ b/include/openspace/interaction/joystickcamerastates.h @@ -54,7 +54,7 @@ public: }; enum class JoystickType { - JoystickLike = 0, + JoystickLike, TriggerLike }; @@ -88,14 +88,14 @@ public: void updateStateFromInput( const JoystickInputStates& joystickInputStates, double deltaTime); - void setAxisMapping(const std::string& joystickName, int axis, AxisType mapping, + void setAxisMapping(std::string joystickName, int axis, AxisType mapping, AxisInvert shouldInvert = AxisInvert::No, JoystickType joystickType = JoystickType::JoystickLike, bool isSticky = false, double sensitivity = 0.0 ); - void setAxisMappingProperty(const std::string& joystickName, int axis, - const std::string& propertyUri, float min = 0.f, float max = 1.f, + void setAxisMappingProperty(std::string joystickName, int axis, + std::string propertyUri, float min = 0.f, float max = 1.f, AxisInvert shouldInvert = AxisInvert::No, bool isRemote = true ); @@ -119,7 +119,6 @@ private: // 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; // This array is used to store the old axis values from the previous frame, @@ -127,9 +126,16 @@ private: std::array prevAxisValues; struct ButtonInformation { + // The script that is run when the button is activated std::string command; + + // When is the button considered activated JoystickAction action; + + // If the script should be syncronised to other remote sessions or not ButtonCommandRemote synchronization; + + // Short documentation on what the script of this button does std::string documentation; }; @@ -140,8 +146,8 @@ private: // Find the item in _joystickCameraStates that corresponds to the given joystickName // return a pointer to the item, if not found then return nullptr - JoystickCameraState* getJoystickCameraState(const std::string& joystickName); - const JoystickCameraState* getJoystickCameraState(const std::string& joystickName) const; + JoystickCameraState* joystickCameraState(const std::string& joystickName); + const JoystickCameraState* joystickCameraState(const std::string& joystickName) const; // Ues getJoystickCameraState(name) to find the joystickCameraState that // corresponds to the given joystickName. If not found then add a new item if possible @@ -204,9 +210,9 @@ inline std::string to_string( { using T = openspace::interaction::JoystickCameraStates::JoystickType; switch (value) { - case T::JoystickLike: return "JoystickLike"; - case T::TriggerLike: return "TriggerLike"; - default: return ""; + case T::JoystickLike: return "JoystickLike"; + case T::TriggerLike: return "TriggerLike"; + default: return ""; } } diff --git a/include/openspace/navigation/navigationhandler.h b/include/openspace/navigation/navigationhandler.h index 3a6bbeac36..f66e765ac5 100644 --- a/include/openspace/navigation/navigationhandler.h +++ b/include/openspace/navigation/navigationhandler.h @@ -98,7 +98,7 @@ public: void mousePositionCallback(double x, double y); void mouseScrollWheelCallback(double pos); - void setJoystickAxisMapping(const std::string& joystickName, + void setJoystickAxisMapping(std::string joystickName, int axis, JoystickCameraStates::AxisType mapping, JoystickCameraStates::AxisInvert shouldInvert = JoystickCameraStates::AxisInvert::No, @@ -107,8 +107,8 @@ public: bool isSticky = false, double sensitivity = 0.0 ); - void setJoystickAxisMappingProperty(const std::string& joystickName, - int axis, const std::string& propertyUri, + void setJoystickAxisMappingProperty(std::string joystickName, + int axis, std::string propertyUri, float min = 0.f, float max = 1.f, JoystickCameraStates::AxisInvert shouldInvert = JoystickCameraStates::AxisInvert::No, bool isRemote = true diff --git a/src/interaction/joystickcamerastates.cpp b/src/interaction/joystickcamerastates.cpp index 5a78810870..55e775f230 100644 --- a/src/interaction/joystickcamerastates.cpp +++ b/src/interaction/joystickcamerastates.cpp @@ -56,17 +56,16 @@ void JoystickCameraStates::updateStateFromInput( continue; } - JoystickCameraState* joystickCameraState = - getJoystickCameraState(joystickInputState.name); + JoystickCameraState* joystick = joystickCameraState(joystickInputState.name); - if (!joystickCameraState) { + if (!joystick) { continue; } for (int i = 0; i < JoystickInputState::MaxAxes; ++i) { std::string oscLable = joystickInputState.name; - AxisInformation t = joystickCameraState->axisMapping[i]; + AxisInformation t = joystick->axisMapping[i]; if (t.type == AxisType::None) { continue; } @@ -75,13 +74,13 @@ void JoystickCameraStates::updateStateFromInput( float value = rawValue; if (t.isSticky) { - value = rawValue - joystickCameraState->prevAxisValues[i]; - joystickCameraState->prevAxisValues[i] = rawValue; + value = rawValue - joystick->prevAxisValues[i]; + joystick->prevAxisValues[i] = rawValue; } if ((t.joystickType == JoystickType::JoystickLike && - std::fabs(value) <= t.deadzone) || - (t.joystickType == JoystickType::TriggerLike && value <= -1 + t.deadzone)) + std::abs(value) <= t.deadzone) || + (t.joystickType == JoystickType::TriggerLike && value <= -1.f + t.deadzone)) { continue; } @@ -163,8 +162,8 @@ void JoystickCameraStates::updateStateFromInput( oscLable += "_PanY"; break; case AxisType::Property: - std::string script = "openspace.setPropertyValue(\"" + - t.propertyUri + "\", " + std::to_string(value) + ");"; + std::string script = fmt::format("openspace.setPropertyValue('{}', {});", + t.propertyUri, value); global::scriptEngine->queueScript( script, @@ -187,7 +186,7 @@ void JoystickCameraStates::updateStateFromInput( for (int i = 0; i < JoystickInputState::MaxButtons; ++i) { std::string oscLable = joystickInputState.name + "_button_" + std::to_string(i + 1); - auto itRange = joystickCameraState->buttonMapping.equal_range(i); + auto itRange = joystick->buttonMapping.equal_range(i); for (auto it = itRange.first; it != itRange.second; ++it) { bool active = global::joystickInputStates->button( joystickInputState.name, @@ -248,7 +247,7 @@ void JoystickCameraStates::updateStateFromInput( } } -void JoystickCameraStates::setAxisMapping(const std::string& joystickName, +void JoystickCameraStates::setAxisMapping(std::string joystickName, int axis, AxisType mapping, AxisInvert shouldInvert, JoystickType joystickType, @@ -272,9 +271,9 @@ void JoystickCameraStates::setAxisMapping(const std::string& joystickName, global::joystickInputStates->axis(joystickName, axis); } -void JoystickCameraStates::setAxisMappingProperty(const std::string& joystickName, +void JoystickCameraStates::setAxisMappingProperty(std::string joystickName, int axis, - const std::string& propertyUri, + std::string propertyUri, float min, float max, AxisInvert shouldInvert, bool isRemote) @@ -301,13 +300,13 @@ JoystickCameraStates::AxisInformation JoystickCameraStates::axisMapping( const std::string& joystickName, int axis) const { - const JoystickCameraState* joystickCameraState = getJoystickCameraState(joystickName); - if (!joystickCameraState) { + const JoystickCameraState* joystick = joystickCameraState(joystickName); + if (!joystick) { JoystickCameraStates::AxisInformation dummy; return dummy; } - return joystickCameraState->axisMapping[axis]; + return joystick->axisMapping[axis]; } void JoystickCameraStates::setDeadzone(const std::string& joystickName, int axis, @@ -322,12 +321,12 @@ void JoystickCameraStates::setDeadzone(const std::string& joystickName, int axis } float JoystickCameraStates::deadzone(const std::string& joystickName, int axis) const { - const JoystickCameraState* joystickCameraState = getJoystickCameraState(joystickName); - if (!joystickCameraState) { - return 0.0f; + const JoystickCameraState* joystick = joystickCameraState(joystickName); + if (!joystick) { + return 0.f; } - return joystickCameraState->axisMapping[axis].deadzone; + return joystick->axisMapping[axis].deadzone; } void JoystickCameraStates::bindButtonCommand(const std::string& joystickName, @@ -350,18 +349,18 @@ void JoystickCameraStates::bindButtonCommand(const std::string& joystickName, void JoystickCameraStates::clearButtonCommand(const std::string& joystickName, int button) { - JoystickCameraState* joystickCameraState = getJoystickCameraState(joystickName); - if (!joystickCameraState) { + JoystickCameraState* joystick = joystickCameraState(joystickName); + if (!joystick) { return; } - for (auto it = joystickCameraState->buttonMapping.begin(); - it != joystickCameraState->buttonMapping.end(); ) + for (auto it = joystick->buttonMapping.begin(); + it != joystick->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 = joystickCameraState->buttonMapping.erase(it); + it = joystick->buttonMapping.erase(it); } else { ++it; @@ -374,19 +373,19 @@ std::vector JoystickCameraStates::buttonCommand( int button) const { std::vector result; - const JoystickCameraState* joystickCameraState = getJoystickCameraState(joystickName); - if (!joystickCameraState) { + const JoystickCameraState* joystick = joystickCameraState(joystickName); + if (!joystick) { return result; } - auto itRange = joystickCameraState->buttonMapping.equal_range(button); + auto itRange = joystick->buttonMapping.equal_range(button); for (auto it = itRange.first; it != itRange.second; ++it) { result.push_back(it->second.command); } return result; } -JoystickCameraStates::JoystickCameraState* JoystickCameraStates::getJoystickCameraState( +JoystickCameraStates::JoystickCameraState* JoystickCameraStates::joystickCameraState( const std::string& joystickName) { for (JoystickCameraState& joystickCameraState : _joystickCameraStates) { @@ -399,7 +398,7 @@ JoystickCameraStates::JoystickCameraState* JoystickCameraStates::getJoystickCame } const JoystickCameraStates::JoystickCameraState* - JoystickCameraStates::getJoystickCameraState(const std::string& joystickName) const +JoystickCameraStates::joystickCameraState(const std::string& joystickName) const { for (const JoystickCameraState& joystickCameraState : _joystickCameraStates) { if (joystickCameraState.joystickName == joystickName) { @@ -412,14 +411,14 @@ const JoystickCameraStates::JoystickCameraState* } JoystickCameraStates::JoystickCameraState* - JoystickCameraStates::findOrAddJoystickCameraState(const std::string& joystickName) +JoystickCameraStates::findOrAddJoystickCameraState(const std::string& joystickName) { - JoystickCameraState* joystickCameraState = getJoystickCameraState(joystickName); - if (!joystickCameraState) { + JoystickCameraState* joystick = joystickCameraState(joystickName); + if (!joystick) { if (_joystickCameraStates.size() < JoystickInputStates::MaxNumJoysticks) { _joystickCameraStates.push_back(JoystickCameraState()); - joystickCameraState = &_joystickCameraStates.back(); - joystickCameraState->joystickName = joystickName; + joystick = &_joystickCameraStates.back(); + joystick->joystickName = joystickName; } else { LWARNING(fmt::format("Cannot add more joysticks, only {} joysticks are " @@ -427,7 +426,7 @@ JoystickCameraStates::JoystickCameraState* return nullptr; } } - return joystickCameraState; + return joystick; } diff --git a/src/interaction/joystickinputstate.cpp b/src/interaction/joystickinputstate.cpp index 9c0da27225..170a40e296 100644 --- a/src/interaction/joystickinputstate.cpp +++ b/src/interaction/joystickinputstate.cpp @@ -36,7 +36,7 @@ namespace openspace::interaction { float JoystickInputStates::axis(const std::string& joystickName, int axis) const { ghoul_precondition(axis >= 0, "axis must be 0 or positive"); - if(joystickName.empty()) { + if (joystickName.empty()) { float res = std::accumulate( begin(), end(), @@ -63,7 +63,7 @@ float JoystickInputStates::axis(const std::string& joystickName, int axis) const } if (!state) { - return 0.0f; + return 0.f; } return state->axes[axis]; @@ -72,7 +72,7 @@ float JoystickInputStates::axis(const std::string& joystickName, int axis) const bool JoystickInputStates::button(const std::string& joystickName, int button, JoystickAction action) const { ghoul_precondition(button >= 0, "button must be 0 or positive"); - if(joystickName.empty()) { + if (joystickName.empty()) { bool res = std::any_of( begin(), end(), diff --git a/src/navigation/navigationhandler.cpp b/src/navigation/navigationhandler.cpp index a377a049ed..de3fa71d26 100644 --- a/src/navigation/navigationhandler.cpp +++ b/src/navigation/navigationhandler.cpp @@ -502,7 +502,7 @@ void NavigationHandler::loadNavigationState(const std::string& filepath) { } } -void NavigationHandler::setJoystickAxisMapping(const std::string& joystickName, int axis, +void NavigationHandler::setJoystickAxisMapping(std::string joystickName, int axis, JoystickCameraStates::AxisType mapping, JoystickCameraStates::AxisInvert shouldInvert, JoystickCameraStates::JoystickType joystickType, @@ -510,7 +510,7 @@ void NavigationHandler::setJoystickAxisMapping(const std::string& joystickName, double sensitivity) { _orbitalNavigator.joystickStates().setAxisMapping( - joystickName, + std::move(joystickName), axis, mapping, shouldInvert, @@ -520,17 +520,17 @@ void NavigationHandler::setJoystickAxisMapping(const std::string& joystickName, ); } -void NavigationHandler::setJoystickAxisMappingProperty(const std::string& joystickName, +void NavigationHandler::setJoystickAxisMappingProperty(std::string joystickName, int axis, - const std::string& propertyUri, + std::string propertyUri, float min, float max, JoystickCameraStates::AxisInvert shouldInvert, bool isRemote) { _orbitalNavigator.joystickStates().setAxisMappingProperty( - joystickName, + std::move(joystickName), axis, - propertyUri, + std::move(propertyUri), min, max, shouldInvert, diff --git a/src/navigation/navigationhandler_lua.inl b/src/navigation/navigationhandler_lua.inl index 9be6957ce2..85a9034d78 100644 --- a/src/navigation/navigationhandler_lua.inl +++ b/src/navigation/navigationhandler_lua.inl @@ -164,7 +164,7 @@ int bindJoystickAxis(lua_State* L) { joystickType = joystickType.value_or("JoystickLike"); global::navigationHandler->setJoystickAxisMapping( - joystickName, + std::move(joystickName), axis, ghoul::from_string(axisType), interaction::JoystickCameraStates::AxisInvert(*shouldInvert), @@ -176,7 +176,7 @@ int bindJoystickAxis(lua_State* L) { } int bindJoystickAxisProperty(lua_State* L) { - ghoul::lua::checkArgumentsAndThrow(L, { 3, 9 }, "lua::bindJoystickAxisProperty"); + ghoul::lua::checkArgumentsAndThrow(L, { 3, 7 }, "lua::bindJoystickAxisProperty"); auto [joystickName, axis, propertyUri, min, max, shouldInvert, isRemote] = ghoul::lua::values< std::string, int, std::string, std::optional, std::optional, @@ -188,9 +188,9 @@ int bindJoystickAxisProperty(lua_State* L) { isRemote = isRemote.value_or(true); global::navigationHandler->setJoystickAxisMappingProperty( - joystickName, + std::move(joystickName), axis, - propertyUri, + std::move(propertyUri), *min, *max, interaction::JoystickCameraStates::AxisInvert(*shouldInvert),