diff --git a/apps/OpenSpace/main.cpp b/apps/OpenSpace/main.cpp index a4e914a7ed..90bd7dda8c 100644 --- a/apps/OpenSpace/main.cpp +++ b/apps/OpenSpace/main.cpp @@ -207,6 +207,71 @@ LONG WINAPI generateMiniDump(EXCEPTION_POINTERS* exceptionPointers) { } #endif // WIN32 +void checkJoystickStatus() { + using namespace interaction; + + for (int i = GLFW_JOYSTICK_1; i <= GLFW_JOYSTICK_LAST; ++i) { + ZoneScopedN("Joystick state"); + + JoystickInputState& state = global::joystickInputStates->at(i); + + int present = glfwJoystickPresent(i); + if (present == GLFW_FALSE) { + state.isConnected = false; + continue; + } + + if (!state.isConnected) { + // Joystick was added + state.isConnected = true; + state.name = glfwGetJoystickName(i); + + 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); + glfwGetJoystickButtons(i, &state.nButtons); + } + + const float* axes = glfwGetJoystickAxes(i, &state.nAxes); + state.axes.resize(state.nAxes); + std::memcpy(state.axes.data(), axes, state.nAxes * sizeof(float)); + + const unsigned char* buttons = glfwGetJoystickButtons(i, &state.nButtons); + state.buttons.resize(state.nButtons); + + for (int j = 0; j < state.nButtons; ++j) { + const 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; + } + } + } + } +} + // // Init function // @@ -319,6 +384,9 @@ void mainInitFunc(GLFWwindow*) { #endif // OPENSPACE_HAS_SPOUT } + // Query joystick status, those connected before start up + checkJoystickStatus(); + LTRACE("main::mainInitFunc(end)"); } @@ -336,87 +404,8 @@ void mainPreSyncFunc() { Engine::instance().terminate(); } - // Query joystick status - using namespace interaction; - - for (int i = GLFW_JOYSTICK_1; i <= GLFW_JOYSTICK_LAST; ++i) { - ZoneScopedN("Joystick state"); - - JoystickInputState& state = global::joystickInputStates->at(i); - - int present = glfwJoystickPresent(i); - if (present == GLFW_FALSE) { - state.isConnected = false; - continue; - } - - if (!state.isConnected) { - // Joystick was added - state.isConnected = true; - state.name = glfwGetJoystickName(i); - - 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) { - 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) { - state.nButtons = JoystickInputState::MaxButtons; - } - - for (int j = 0; j < state.nButtons; ++j) { - const 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; - } - } - } - } + // Query joystick status, those connected at run time + checkJoystickStatus(); LTRACE("main::mainPreSyncFunc(end)"); } diff --git a/data/assets/examples/joystickProperty.asset b/data/assets/examples/joystickProperty.asset index 04088a783a..bcd71b14e9 100644 --- a/data/assets/examples/joystickProperty.asset +++ b/data/assets/examples/joystickProperty.asset @@ -25,8 +25,8 @@ -- 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 +-- Fourth - (optional) The smallest value that you want to allow this property on the joystick +-- Fifth - (optional) The largest value that you want 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 diff --git a/data/assets/util/joysticks/any-joystick.asset b/data/assets/util/joysticks/any-joystick.asset new file mode 100644 index 0000000000..d53777aff0 --- /dev/null +++ b/data/assets/util/joysticks/any-joystick.asset @@ -0,0 +1,72 @@ +local propertyHelper = asset.require("../property_helper") +local joystickHelper = asset.require("./joystick_helper") +local initializeAll = false + +-- Add the asset cooresponding to the joystick name +local function addJoystickAsset(joystick) + if joystick == "Wireless Controller" then + openspace.asset.add("./util/joysticks/ps4") + elseif joystick == "Xbox Controller" then + openspace.asset.add("./util/joysticks/xbox") + elseif joystick == "Wireless Xbox Controller" then + openspace.asset.add("./util/joysticks/xbox-wireless") + elseif joystick == "SpaceNavigator" then + openspace.asset.add("./util/joysticks/space-mouse-compact") + elseif joystick == "SpaceMouse Enterprise" then + openspace.asset.add("./util/joysticks/space-mouse-enterprise") + elseif joystick == "3Dconnexion Universal Receiver" then + openspace.printWarning("SpaceMouse in wireless mode cannot be automatically detected and added. Please add the matching asset file manually.") + else + openspace.printWarning("Could not find a matching asset for joystick: " .. joystick) + end +end + +asset.onInitialize(function() + local rawJoysticks = openspace.navigation.listAllJoysticks() + local numJoysticks = #rawJoysticks + local joystick = "" + + -- Remove the SpaceMouse emulator if it exists + local joysticks = {} + for _, joyStickName in ipairs(rawJoysticks) do + if joyStickName == "3Dconnexion KMJ Emulator" then + numJoysticks = numJoysticks - 1 + else + table.insert(joysticks, joyStickName) + end + end + + if numJoysticks == 0 then + openspace.printWarning("Could not find any connected joysticks") + elseif numJoysticks == 1 then + addJoystickAsset(joysticks[1]) + elseif numJoysticks > 1 and initializeAll then + for _, joyStickName in ipairs(joysticks) do + addJoystickAsset(joyStickName) + end + else + openspace.printWarning("Cannot auto detect and add several joysticks") + end +end) + +asset.onDeinitialize(function() + if openspace.asset.isLoaded("./util/joysticks/ps4") then + openspace.asset.remove("./util/joysticks/ps4") + end + + if openspace.asset.isLoaded("./util/joysticks/xbox") then + openspace.asset.remove("./util/joysticks/xbox") + end + + if openspace.asset.isLoaded("./util/joysticks/xbox-wireless") then + openspace.asset.remove("./util/joysticks/xbox-wireless") + end + + if openspace.asset.isLoaded("./util/joysticks/space-mouse-compact") then + openspace.asset.remove("./util/joysticks/space-mouse-compact") + end + + if openspace.asset.isLoaded("./util/joysticks/space-mouse-enterprise") then + openspace.asset.remove("./util/joysticks/space-mouse-enterprise") + end +end) diff --git a/data/assets/util/joysticks/joystick_helper.asset b/data/assets/util/joysticks/joystick_helper.asset index 66e376d1f9..f803711282 100644 --- a/data/assets/util/joysticks/joystick_helper.asset +++ b/data/assets/util/joysticks/joystick_helper.asset @@ -63,8 +63,27 @@ local unbindRoll = function(name, axis) ]] end + +-- Function that will find the first connected joystick with a name that matches one of +-- the given possible names +local findJoystick = function(possibleNames) + -- Will only catch the joysticks that are connected before start up + local joysticks = openspace.navigation.listAllJoysticks() + + for _, joyStickName in ipairs(joysticks) do + for _, name in ipairs(possibleNames) do + if joyStickName == name then + return name + end + end + end + + return nil +end + asset.export("bindLocalRoll", bindLocalRoll) asset.export("bindGlobalRoll", bindGlobalRoll) asset.export("permaBindLocalRoll", permaBindLocalRoll) asset.export("permaBindGlobalRoll", permaBindGlobalRoll) asset.export("unbindRoll", unbindRoll) +asset.export("findJoystick", findJoystick) diff --git a/data/assets/util/joysticks/ps4.asset b/data/assets/util/joysticks/ps4.asset index 829661f96a..40db2d4533 100644 --- a/data/assets/util/joysticks/ps4.asset +++ b/data/assets/util/joysticks/ps4.asset @@ -52,12 +52,14 @@ asset.onInitialize(function() local controller = PS4Controller; local name = "Wireless Controller"; - openspace.navigation.setAxisDeadZone(name, controller.LeftThumbStick[1], 0.15) - openspace.navigation.setAxisDeadZone(name, controller.LeftThumbStick[2], 0.15) - openspace.navigation.setAxisDeadZone(name, controller.RightThumbStick[1], 0.15) - openspace.navigation.setAxisDeadZone(name, controller.RightThumbStick[2], 0.15) - openspace.navigation.setAxisDeadZone(name, controller.L2, 0.05) - openspace.navigation.setAxisDeadZone(name, controller.R2, 0.05) + local deadzoneJoysticks = 0.15 + local deadzoneTriggers = 0.05 + openspace.navigation.setAxisDeadZone(name, controller.LeftThumbStick[1], deadzoneJoysticks) + openspace.navigation.setAxisDeadZone(name, controller.LeftThumbStick[2], deadzoneJoysticks) + openspace.navigation.setAxisDeadZone(name, controller.RightThumbStick[1], deadzoneJoysticks) + openspace.navigation.setAxisDeadZone(name, controller.RightThumbStick[2], deadzoneJoysticks) + openspace.navigation.setAxisDeadZone(name, controller.L2, deadzoneTriggers) + openspace.navigation.setAxisDeadZone(name, controller.R2, deadzoneTriggers) openspace.navigation.bindJoystickAxis(name, controller.LeftThumbStick[1], "Orbit X"); openspace.navigation.bindJoystickAxis(name, controller.LeftThumbStick[2], "Orbit Y", true); @@ -116,9 +118,9 @@ asset.onInitialize(function() name, controller.Square, [[ - "openspace.setPropertyValueSingle("NavigationHandler.OrbitalNavigator.Aim", ""); - "openspace.setPropertyValueSingle("NavigationHandler.OrbitalNavigator.Anchor", "Earth"); - "openspace.setPropertyValueSingle("NavigationHandler.OrbitalNavigator.RetargetAnchor", nil); + openspace.setPropertyValueSingle("NavigationHandler.OrbitalNavigator.Aim", ""); + openspace.setPropertyValueSingle("NavigationHandler.OrbitalNavigator.Anchor", "Earth"); + openspace.setPropertyValueSingle("NavigationHandler.OrbitalNavigator.RetargetAnchor", nil); ]], "Switch target to Earth" ) diff --git a/data/assets/util/joysticks/space-mouse-not-sticky.asset b/data/assets/util/joysticks/space-mouse-compact-wireless.asset similarity index 83% rename from data/assets/util/joysticks/space-mouse-not-sticky.asset rename to data/assets/util/joysticks/space-mouse-compact-wireless.asset index cea4fb0681..2874aa23f2 100644 --- a/data/assets/util/joysticks/space-mouse-not-sticky.asset +++ b/data/assets/util/joysticks/space-mouse-compact-wireless.asset @@ -21,27 +21,28 @@ local joystickHelper = asset.require("./joystick_helper") -- 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. --- This version of the SpaceMouse is NOT Sticky. +-- This version of the SpaceMouse IS Sticky. -- Seventh parameter is the sensitivity for the axis local SpaceMouse = { Push = {0, 1, 2}, -- left/right, back/forth, up/down Twist = {5}, -- left/right Tilt = {4, 3}, -- left/right, back/forth + LeftButton = 0, RightButton = 1 } asset.onInitialize(function() local controller = SpaceMouse; - local name = "SpaceNavigator"; + local name = "3Dconnexion Universal Receiver"; - openspace.navigation.bindJoystickAxis(name, controller.Push[1], "Orbit X"); - openspace.navigation.bindJoystickAxis(name, controller.Push[2], "Orbit Y"); - openspace.navigation.bindJoystickAxis(name, controller.Twist[1], "Pan X", true); - openspace.navigation.bindJoystickAxis(name, controller.Tilt[2], "Pan Y"); - openspace.navigation.bindJoystickAxis(name, controller.Push[3], "Zoom"); - openspace.navigation.bindJoystickAxis(name, controller.Tilt[1], "LocalRoll X"); + openspace.navigation.bindJoystickAxis(name, controller.Push[1], "Orbit X", false, "JoystickLike", true, 40.0); + openspace.navigation.bindJoystickAxis(name, controller.Push[2], "Orbit Y", false, "JoystickLike", true, 40.0); + openspace.navigation.bindJoystickAxis(name, controller.Twist[1], "Pan X", true, "JoystickLike", true, 40.0); + openspace.navigation.bindJoystickAxis(name, controller.Tilt[2], "Pan Y", false, "JoystickLike", true, 35.0); + openspace.navigation.bindJoystickAxis(name, controller.Push[3], "Zoom", false, "JoystickLike", true, 40.0); + openspace.navigation.bindJoystickAxis(name, controller.Tilt[1], "LocalRoll X", false, "JoystickLike", true, 35.0); openspace.navigation.bindJoystickButton( name, diff --git a/data/assets/util/joysticks/space-mouse.asset b/data/assets/util/joysticks/space-mouse-compact.asset similarity index 99% rename from data/assets/util/joysticks/space-mouse.asset rename to data/assets/util/joysticks/space-mouse-compact.asset index 3d36804891..d9a2d13fed 100644 --- a/data/assets/util/joysticks/space-mouse.asset +++ b/data/assets/util/joysticks/space-mouse-compact.asset @@ -28,6 +28,7 @@ local SpaceMouse = { Push = {0, 1, 2}, -- left/right, back/forth, up/down Twist = {5}, -- left/right Tilt = {4, 3}, -- left/right, back/forth + LeftButton = 0, RightButton = 1 } diff --git a/data/assets/util/joysticks/space-mouse-enterprise-wireless.asset b/data/assets/util/joysticks/space-mouse-enterprise-wireless.asset new file mode 100644 index 0000000000..2a8f124196 --- /dev/null +++ b/data/assets/util/joysticks/space-mouse-enterprise-wireless.asset @@ -0,0 +1,54 @@ +local propertyHelper = asset.require("../property_helper") +local joystickHelper = asset.require("./joystick_helper") + +-- 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. +-- This version of the SpaceMouse is NOT Sticky. +-- Seventh parameter is the sensitivity for the axis + +local SpaceMouse = { + Push = {0, 1, 2}, -- left/right, back/forth, up/down + Twist = {5}, -- left/right + Tilt = {4, 3}, -- left/right, back/forth + + -- Buttons on the Enterprise version of the SpaceMouse is not detectable, use regular + -- keybindings instead, for more information see our wiki page. +} + +asset.onInitialize(function() + local controller = SpaceMouse; + local name = "3Dconnexion Universal Receiver"; + + local deadzone = 0.15 + openspace.navigation.setAxisDeadZone(name, controller.Push[1], deadzone) + openspace.navigation.setAxisDeadZone(name, controller.Push[2], deadzone) + openspace.navigation.setAxisDeadZone(name, controller.Twist[1], deadzone) + openspace.navigation.setAxisDeadZone(name, controller.Tilt[2], deadzone) + openspace.navigation.setAxisDeadZone(name, controller.Push[3], deadzone) + openspace.navigation.setAxisDeadZone(name, controller.Tilt[1], deadzone) + + openspace.navigation.bindJoystickAxis(name, controller.Push[1], "Orbit X"); + openspace.navigation.bindJoystickAxis(name, controller.Push[2], "Orbit Y"); + openspace.navigation.bindJoystickAxis(name, controller.Twist[1], "Pan X", true); + openspace.navigation.bindJoystickAxis(name, controller.Tilt[2], "Pan Y"); + openspace.navigation.bindJoystickAxis(name, controller.Push[3], "Zoom"); + openspace.navigation.bindJoystickAxis(name, controller.Tilt[1], "LocalRoll X"); +end) diff --git a/data/assets/util/joysticks/space-mouse-enterprise.asset b/data/assets/util/joysticks/space-mouse-enterprise.asset new file mode 100644 index 0000000000..cb9510a641 --- /dev/null +++ b/data/assets/util/joysticks/space-mouse-enterprise.asset @@ -0,0 +1,54 @@ +local propertyHelper = asset.require("../property_helper") +local joystickHelper = asset.require("./joystick_helper") + +-- 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. +-- This version of the SpaceMouse is NOT Sticky. +-- Seventh parameter is the sensitivity for the axis + +local SpaceMouse = { + Push = {0, 1, 2}, -- left/right, back/forth, up/down + Twist = {5}, -- left/right + Tilt = {4, 3}, -- left/right, back/forth + + -- Buttons on the Enterprise version of the SpaceMouse is not detectable, use regular + -- keybindings instead, for more information see our wiki page. +} + +asset.onInitialize(function() + local controller = SpaceMouse; + local name = "SpaceMouse Enterprise"; + + local deadzone = 0.15 + openspace.navigation.setAxisDeadZone(name, controller.Push[1], deadzone) + openspace.navigation.setAxisDeadZone(name, controller.Push[2], deadzone) + openspace.navigation.setAxisDeadZone(name, controller.Twist[1], deadzone) + openspace.navigation.setAxisDeadZone(name, controller.Tilt[2], deadzone) + openspace.navigation.setAxisDeadZone(name, controller.Push[3], deadzone) + openspace.navigation.setAxisDeadZone(name, controller.Tilt[1], deadzone) + + openspace.navigation.bindJoystickAxis(name, controller.Push[1], "Orbit X"); + openspace.navigation.bindJoystickAxis(name, controller.Push[2], "Orbit Y"); + openspace.navigation.bindJoystickAxis(name, controller.Twist[1], "Pan X", true); + openspace.navigation.bindJoystickAxis(name, controller.Tilt[2], "Pan Y"); + openspace.navigation.bindJoystickAxis(name, controller.Push[3], "Zoom"); + openspace.navigation.bindJoystickAxis(name, controller.Tilt[1], "LocalRoll X"); +end) diff --git a/data/assets/util/joysticks/xbox-wireless.asset b/data/assets/util/joysticks/xbox-wireless.asset new file mode 100644 index 0000000000..aaef2fc79d --- /dev/null +++ b/data/assets/util/joysticks/xbox-wireless.asset @@ -0,0 +1,135 @@ +local propertyHelper = asset.require("../property_helper") +local joystickHelper = asset.require("./joystick_helper") + +-- 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 + +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 + } +} + +asset.onInitialize(function() + local controller = XBoxController; + local name = "Wireless Xbox Controller"; + + local deadzoneJoysticks = 0.15 + local deadzoneTriggers = 0.05 + openspace.navigation.setAxisDeadZone(name, controller.LeftThumbStick[1], deadzoneJoysticks) + openspace.navigation.setAxisDeadZone(name, controller.LeftThumbStick[2], deadzoneJoysticks) + openspace.navigation.setAxisDeadZone(name, controller.RightThumbStick[1], deadzoneJoysticks) + openspace.navigation.setAxisDeadZone(name, controller.RightThumbStick[2], deadzoneJoysticks) + openspace.navigation.setAxisDeadZone(name, controller.LeftTrigger, deadzoneTriggers) + openspace.navigation.setAxisDeadZone(name, controller.RightTrigger, deadzoneTriggers) + + openspace.navigation.bindJoystickAxis(name, controller.LeftThumbStick[1], "Orbit X"); + openspace.navigation.bindJoystickAxis(name, controller.LeftThumbStick[2], "Orbit Y", true); + openspace.navigation.bindJoystickAxis(name, controller.RightThumbStick[1], "Pan X", true); + openspace.navigation.bindJoystickAxis(name, controller.RightThumbStick[2], "Pan Y", true); + openspace.navigation.bindJoystickAxis(name, controller.LeftTrigger, "Zoom Out", false, "TriggerLike"); + openspace.navigation.bindJoystickAxis(name, controller.RightTrigger, "Zoom In", false, "TriggerLike"); + + openspace.navigation.bindJoystickButton( + name, + controller.LB, + joystickHelper.bindLocalRoll(name, controller.RightThumbStick[1]), + "Switch to local roll mode" + ) + openspace.navigation.bindJoystickButton( + name, + controller.LB, + joystickHelper.unbindRoll(name, controller.RightThumbStick[1]), + "Switch back to normal mode", + "Release" + ) + openspace.navigation.bindJoystickButton( + name, + controller.RB, + joystickHelper.bindGlobalRoll(name, controller.RightThumbStick[1]), + "Switch to global roll mode" + ) + openspace.navigation.bindJoystickButton( + name, + controller.RB, + joystickHelper.unbindRoll(name, controller.RightThumbStick[1]), + "Switch back to normal mode", + "Release" + ) + + openspace.navigation.bindJoystickButton( + name, + controller.A, + propertyHelper.invert("NavigationHandler.OrbitalNavigator.Friction.ZoomFriction"), + "Toggle zoom friction" + ) + openspace.navigation.bindJoystickButton( + name, + controller.B, + propertyHelper.invert("NavigationHandler.OrbitalNavigator.Friction.RotationalFriction"), + "Toggle rotational friction" + ) + openspace.navigation.bindJoystickButton( + name, + controller.DPad.Left, + propertyHelper.invert("NavigationHandler.OrbitalNavigator.Friction.RollFriction"), + "Toggle roll friction" + ) + + openspace.navigation.bindJoystickButton( + name, + controller.X, + [[ + openspace.setPropertyValueSingle("NavigationHandler.OrbitalNavigator.Aim", ""); + openspace.setPropertyValueSingle("NavigationHandler.OrbitalNavigator.Anchor", "Earth"); + openspace.setPropertyValueSingle("NavigationHandler.OrbitalNavigator.RetargetAnchor", nil); + ]], + "Switch target to Earth" + ) + openspace.navigation.bindJoystickButton( + name, + controller.Y, + [[ + openspace.setPropertyValueSingle("NavigationHandler.OrbitalNavigator.Aim", ""); + openspace.setPropertyValueSingle("NavigationHandler.OrbitalNavigator.Anchor", "Mars"); + openspace.setPropertyValueSingle("NavigationHandler.OrbitalNavigator.RetargetAnchor", nil); + ]], + "Switch target to Mars" + ) +end) diff --git a/data/assets/util/joysticks/xbox.asset b/data/assets/util/joysticks/xbox.asset index c94449ba68..f4389e83f1 100644 --- a/data/assets/util/joysticks/xbox.asset +++ b/data/assets/util/joysticks/xbox.asset @@ -50,12 +50,14 @@ asset.onInitialize(function() local controller = XBoxController; local name = "Xbox Controller"; - openspace.navigation.setAxisDeadZone(name, controller.LeftThumbStick[1], 0.15) - openspace.navigation.setAxisDeadZone(name, controller.LeftThumbStick[2], 0.15) - openspace.navigation.setAxisDeadZone(name, controller.RightThumbStick[1], 0.15) - openspace.navigation.setAxisDeadZone(name, controller.RightThumbStick[2], 0.15) - openspace.navigation.setAxisDeadZone(name, controller.LeftTrigger, 0.05) - openspace.navigation.setAxisDeadZone(name, controller.RightTrigger, 0.05) + local deadzoneJoysticks = 0.15 + local deadzoneTriggers = 0.05 + openspace.navigation.setAxisDeadZone(name, controller.LeftThumbStick[1], deadzoneJoysticks) + openspace.navigation.setAxisDeadZone(name, controller.LeftThumbStick[2], deadzoneJoysticks) + openspace.navigation.setAxisDeadZone(name, controller.RightThumbStick[1], deadzoneJoysticks) + openspace.navigation.setAxisDeadZone(name, controller.RightThumbStick[2], deadzoneJoysticks) + openspace.navigation.setAxisDeadZone(name, controller.LeftTrigger, deadzoneTriggers) + openspace.navigation.setAxisDeadZone(name, controller.RightTrigger, deadzoneTriggers) openspace.navigation.bindJoystickAxis(name, controller.LeftThumbStick[1], "Orbit X"); openspace.navigation.bindJoystickAxis(name, controller.LeftThumbStick[2], "Orbit Y", true); @@ -114,9 +116,9 @@ asset.onInitialize(function() name, controller.X, [[ - "openspace.setPropertyValueSingle("NavigationHandler.OrbitalNavigator.Aim", ""); - "openspace.setPropertyValueSingle("NavigationHandler.OrbitalNavigator.Anchor", "Earth"); - "openspace.setPropertyValueSingle("NavigationHandler.OrbitalNavigator.RetargetAnchor", nil); + openspace.setPropertyValueSingle("NavigationHandler.OrbitalNavigator.Aim", ""); + openspace.setPropertyValueSingle("NavigationHandler.OrbitalNavigator.Anchor", "Earth"); + openspace.setPropertyValueSingle("NavigationHandler.OrbitalNavigator.RetargetAnchor", nil); ]], "Switch target to Earth" ) diff --git a/include/openspace/interaction/joystickcamerastates.h b/include/openspace/interaction/joystickcamerastates.h index 6aa19a5b94..2e61f4a666 100644 --- a/include/openspace/interaction/joystickcamerastates.h +++ b/include/openspace/interaction/joystickcamerastates.h @@ -115,15 +115,13 @@ private: struct JoystickCameraState { std::string joystickName; - // 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; + // We use a vector 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 + std::vector axisMapping; - // This array is used to store the old axis values from the previous frame, it is + // This vector is used to store the old axis values from the previous frame, it is // used to calculate the difference in the values in the case of a sticky axis - std::array prevAxisValues; + std::vector prevAxisValues; struct ButtonInformation { // The script that is run when the button is activated diff --git a/include/openspace/interaction/joystickinputstate.h b/include/openspace/interaction/joystickinputstate.h index 06c3b3642c..a37196fbb5 100644 --- a/include/openspace/interaction/joystickinputstate.h +++ b/include/openspace/interaction/joystickinputstate.h @@ -54,12 +54,6 @@ enum class JoystickAction : uint8_t { * The input state of a single joystick. */ struct JoystickInputState { - /// These two are just randomly selected numbers that can be increased if needed - /// The maximum number of supported axes - static constexpr const int MaxAxes = 8; - /// The maximum number of supported buttons - static constexpr const int MaxButtons = 48; - /// Marks whether this joystick is connected. If this value is \c false, all other /// members of this struct are undefined bool isConnected = false; @@ -69,15 +63,13 @@ struct JoystickInputState { /// 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 values for each axis. Each value is in the range [-1, 1] + std::vector 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 status of each button + std::vector buttons; }; /// The maximum number of joysticks that are supported by this system. This number is @@ -88,12 +80,31 @@ struct JoystickInputStates : public std::array /// derived from the available GLFW constants static constexpr const int MaxNumJoysticks = 16; + /** + * This function return the number of axes the joystick with the given name has + * + * \param joystickName The name of the joystick to check how many axes it has, + * if empty the max number of axes for all joysticks are returned + * \return The number of axes for the joystick with the given name + */ + int numAxes(const std::string& joystickName = "") const; + + /** + * This function return the number of buttons the joystick with the given name has + * + * \param joystickName The name of the joystick to check how many buttons it has, + * if empty the max number of buttons for all joysticks are returned + * \return The number of buttons for the joystick with the given name + */ + int numButtons(const std::string& joystickName = "") const; + /** * 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 joystickName The name of the joystick, if empty all joysticks are combined * \param axis The numerical axis for which the values are added * \return The summed axis values of all connected joysticks * @@ -106,6 +117,7 @@ struct JoystickInputStates : public std::array * passed \p action. Any joystick that does not posses the \p button, it will be * ignored. * + * \param joystickName The name of the joystick, if empty all joysticks are combined * \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 diff --git a/include/openspace/navigation/navigationhandler.h b/include/openspace/navigation/navigationhandler.h index ddd33f1156..5a2bfd6be3 100644 --- a/include/openspace/navigation/navigationhandler.h +++ b/include/openspace/navigation/navigationhandler.h @@ -94,6 +94,7 @@ public: void mousePositionCallback(double x, double y); void mouseScrollWheelCallback(double pos); + std::vector listAllJoysticks() const; void setJoystickAxisMapping(std::string joystickName, int axis, JoystickCameraStates::AxisType mapping, JoystickCameraStates::AxisInvert shouldInvert = diff --git a/modules/imgui/src/guijoystickcomponent.cpp b/modules/imgui/src/guijoystickcomponent.cpp index 187290a844..8e06c3a7a6 100644 --- a/modules/imgui/src/guijoystickcomponent.cpp +++ b/modules/imgui/src/guijoystickcomponent.cpp @@ -60,8 +60,9 @@ void GuiJoystickComponent::render() { ImGui::Text("%s", "Axes"); for (int j = 0; j < state.nAxes; ++j) { float f = state.axes[j]; + std::string id = std::to_string(j) + "##" + state.name + "Axis"; ImGui::SliderFloat( - std::to_string(j).c_str(), + id.c_str(), &f, -1.f, 1.f @@ -69,8 +70,9 @@ void GuiJoystickComponent::render() { } ImGui::Text("%s", "Buttons"); for (int j = 0; j < state.nButtons; ++j) { + std::string id = std::to_string(j) + "##" + state.name + "Button"; ImGui::RadioButton( - std::to_string(j).c_str(), + id.c_str(), state.buttons[j] == JoystickAction::Press || state.buttons[j] == JoystickAction::Repeat ); @@ -84,19 +86,21 @@ void GuiJoystickComponent::render() { ImGui::Text("%s", "Summed contributions"); ImGui::Text("%s", "Axes"); - for (int i = 0; i < JoystickInputState::MaxAxes; ++i) { + for (int i = 0; i < global::joystickInputStates->numAxes(); ++i) { float f = global::joystickInputStates->axis("", i); + std::string id = std::to_string(i) + "##" + "TotalAxis"; ImGui::SliderFloat( - std::to_string(i).c_str(), + id.c_str(), &f, -1.f, 1.f ); } ImGui::Text("%s", "Buttons"); - for (int i = 0; i < JoystickInputState::MaxButtons; ++i) { + for (int i = 0; i < global::joystickInputStates->numButtons(); ++i) { + std::string id = std::to_string(i) + "##" + "TotalButton"; ImGui::RadioButton( - std::to_string(i).c_str(), + id.c_str(), global::joystickInputStates->button("", i, JoystickAction::Press) || global::joystickInputStates->button("", i, JoystickAction::Repeat) ); diff --git a/src/interaction/joystickcamerastates.cpp b/src/interaction/joystickcamerastates.cpp index 0e8ab1b985..ef8259dff7 100644 --- a/src/interaction/joystickcamerastates.cpp +++ b/src/interaction/joystickcamerastates.cpp @@ -62,7 +62,8 @@ void JoystickCameraStates::updateStateFromInput( continue; } - for (int i = 0; i < JoystickInputState::MaxAxes; ++i) { + int nAxes = joystickInputStates.numAxes(joystickInputState.name); + for (int i = 0; i < nAxes; ++i) { AxisInformation t = joystick->axisMapping[i]; if (t.type == AxisType::None) { continue; @@ -166,7 +167,8 @@ void JoystickCameraStates::updateStateFromInput( } } - for (int i = 0; i < JoystickInputState::MaxButtons; ++i) { + int nButtons = joystickInputStates.numButtons(joystickInputState.name); + for (int i = 0; i < nButtons; ++i) { auto itRange = joystick->buttonMapping.equal_range(i); for (auto it = itRange.first; it != itRange.second; ++it) { bool active = global::joystickInputStates->button( @@ -230,13 +232,17 @@ void JoystickCameraStates::setAxisMapping(std::string joystickName, bool isSticky, double sensitivity) { - ghoul_assert(axis < JoystickInputState::MaxAxes, "axis must be < MaxAxes"); - JoystickCameraState* joystickCameraState = findOrAddJoystickCameraState(joystickName); if (!joystickCameraState) { return; } + // If the axis index is too big for the vector then resize it to have room + if (axis >= joystickCameraState->axisMapping.size()) { + joystickCameraState->axisMapping.resize(axis + 1); + joystickCameraState->prevAxisValues.resize(axis + 1); + } + joystickCameraState->axisMapping[axis].type = mapping; joystickCameraState->axisMapping[axis].invert = shouldInvert; joystickCameraState->axisMapping[axis].joystickType = joystickType; @@ -254,13 +260,17 @@ void JoystickCameraStates::setAxisMappingProperty(std::string joystickName, AxisInvert shouldInvert, bool isRemote) { - ghoul_assert(axis < JoystickInputState::MaxAxes, "axis must be < MaxAxes"); - JoystickCameraState* joystickCameraState = findOrAddJoystickCameraState(joystickName); if (!joystickCameraState) { return; } + // If the axis index is too big for the vector then resize it to have room + if (axis >= joystickCameraState->axisMapping.size()) { + joystickCameraState->axisMapping.resize(axis + 1); + joystickCameraState->prevAxisValues.resize(axis + 1); + } + joystickCameraState->axisMapping[axis].type = AxisType::Property; joystickCameraState->axisMapping[axis].invert = shouldInvert; joystickCameraState->axisMapping[axis].propertyUri = propertyUri; @@ -282,6 +292,11 @@ JoystickCameraStates::AxisInformation JoystickCameraStates::axisMapping( return dummy; } + if (axis >= joystick->axisMapping.size()) { + JoystickCameraStates::AxisInformation dummy; + return dummy; + } + return joystick->axisMapping[axis]; } @@ -293,6 +308,12 @@ void JoystickCameraStates::setDeadzone(const std::string& joystickName, int axis return; } + // If the axis index is too big for the vector then resize it to have room + if (axis >= joystickCameraState->axisMapping.size()) { + joystickCameraState->axisMapping.resize(axis + 1); + joystickCameraState->prevAxisValues.resize(axis + 1); + } + joystickCameraState->axisMapping[axis].deadzone = deadzone; } @@ -302,6 +323,10 @@ float JoystickCameraStates::deadzone(const std::string& joystickName, int axis) return 0.f; } + if (axis >= joystick->axisMapping.size()) { + return 0.f; + } + return joystick->axisMapping[axis].deadzone; } diff --git a/src/interaction/joystickinputstate.cpp b/src/interaction/joystickinputstate.cpp index c372a8f4d1..7e3ad29088 100644 --- a/src/interaction/joystickinputstate.cpp +++ b/src/interaction/joystickinputstate.cpp @@ -33,6 +33,44 @@ namespace openspace::interaction { +int JoystickInputStates::numAxes(const std::string& joystickName) const { + if (joystickName.empty()) { + int maxNumAxes = -1; + for (auto it = begin(); it < end(); ++it) { + if (it->nAxes > maxNumAxes) { + maxNumAxes = it->nAxes; + } + } + return maxNumAxes; + } + + for (auto it = begin(); it < end(); ++it) { + if (it->name == joystickName) { + return it->nAxes; + } + } + return -1; +} + +int JoystickInputStates::numButtons(const std::string& joystickName) const { + if (joystickName.empty()) { + int maxNumButtons = -1; + for (auto it = begin(); it < end(); ++it) { + if (it->nButtons > maxNumButtons) { + maxNumButtons = it->nButtons; + } + } + return maxNumButtons; + } + + for (auto it = begin(); it < end(); ++it) { + if (it->name == joystickName) { + return it->nButtons; + } + } + return -1; +} + float JoystickInputStates::axis(const std::string& joystickName, int axis) const { ghoul_precondition(axis >= 0, "axis must be 0 or positive"); diff --git a/src/navigation/navigationhandler.cpp b/src/navigation/navigationhandler.cpp index 40de9812aa..bffcb64484 100644 --- a/src/navigation/navigationhandler.cpp +++ b/src/navigation/navigationhandler.cpp @@ -477,6 +477,18 @@ void NavigationHandler::loadNavigationState(const std::string& filepath) { } } +std::vector NavigationHandler::listAllJoysticks() const { + std::vector result; + result.reserve(global::joystickInputStates->size()); + + for (const JoystickInputState& joystickInputState : *global::joystickInputStates) { + if (!joystickInputState.name.empty()) { + result.push_back(joystickInputState.name); + } + } + return result; +} + void NavigationHandler::setJoystickAxisMapping(std::string joystickName, int axis, JoystickCameraStates::AxisType mapping, JoystickCameraStates::AxisInvert shouldInvert, @@ -604,7 +616,8 @@ scripting::LuaLibrary NavigationHandler::luaLibrary() { codegen::lua::AddTruckMovement, codegen::lua::AddLocalRoll, codegen::lua::AddGlobalRoll, - codegen::lua::TriggerIdleBehavior + codegen::lua::TriggerIdleBehavior, + codegen::lua::ListAllJoysticks } }; } diff --git a/src/navigation/navigationhandler_lua.inl b/src/navigation/navigationhandler_lua.inl index 6b284837cf..6b0ff2c8bf 100644 --- a/src/navigation/navigationhandler_lua.inl +++ b/src/navigation/navigationhandler_lua.inl @@ -354,6 +354,14 @@ joystickAxis(std::string joystickName, int axis) } } +/** + * Return the complete list of connected joysticks + */ +[[codegen::luawrap]] std::vector listAllJoysticks() { + using namespace openspace; + return global::navigationHandler->listAllJoysticks(); +} + #include "navigationhandler_lua_codegen.cpp" } // namespace