From 1dcf03d61e7627f17389eeba635920693a79e248 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Fri, 16 Oct 2015 17:15:46 -0700 Subject: [PATCH 1/2] Put standard 'makeinput' functions on the base class --- .../src/controllers/InputDevice.cpp | 25 +++++++++++++++++++ .../controllers/src/controllers/InputDevice.h | 10 +++++++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/libraries/controllers/src/controllers/InputDevice.cpp b/libraries/controllers/src/controllers/InputDevice.cpp index 351d5b6d1d..4b2376d32a 100644 --- a/libraries/controllers/src/controllers/InputDevice.cpp +++ b/libraries/controllers/src/controllers/InputDevice.cpp @@ -57,3 +57,28 @@ UserInputMapper::PoseValue InputDevice::getPose(int channel) const { return UserInputMapper::PoseValue(); } } + +UserInputMapper::Input InputDevice::makeInput(controller::StandardButtonChannel button) { + return UserInputMapper::Input(_deviceID, button, UserInputMapper::ChannelType::BUTTON); +} + +UserInputMapper::Input InputDevice::makeInput(controller::StandardAxisChannel axis) { + return UserInputMapper::Input(_deviceID, axis, UserInputMapper::ChannelType::AXIS); +} + +UserInputMapper::Input InputDevice::makeInput(controller::StandardPoseChannel pose) { + return UserInputMapper::Input(_deviceID, pose, UserInputMapper::ChannelType::POSE); +} + +UserInputMapper::InputPair InputDevice::makePair(controller::StandardButtonChannel button, const QString& name) { + return UserInputMapper::InputPair(makeInput(button), name); +} + +UserInputMapper::InputPair InputDevice::makePair(controller::StandardAxisChannel axis, const QString& name) { + return UserInputMapper::InputPair(makeInput(axis), name); +} + +UserInputMapper::InputPair InputDevice::makePair(controller::StandardPoseChannel pose, const QString& name) { + return UserInputMapper::InputPair(makeInput(pose), name); +} + diff --git a/libraries/controllers/src/controllers/InputDevice.h b/libraries/controllers/src/controllers/InputDevice.h index 4dbb141832..66f7addc58 100644 --- a/libraries/controllers/src/controllers/InputDevice.h +++ b/libraries/controllers/src/controllers/InputDevice.h @@ -11,6 +11,7 @@ #pragma once #include "UserInputMapper.h" +#include "StandardControls.h" // Event types for each controller const unsigned int CONTROLLER_0_EVENT = 1500U; @@ -33,7 +34,7 @@ public: UserInputMapper::PoseValue getPose(int channel) const; virtual void registerToUserInputMapper(UserInputMapper& mapper) = 0; - virtual void assignDefaultInputMapping(UserInputMapper& mapper) = 0; + virtual void assignDefaultInputMapping(UserInputMapper& mapper) {}; // Update call MUST be called once per simulation loop // It takes care of updating the action states and deltas @@ -49,10 +50,17 @@ public: static bool getLowVelocityFilter() { return _lowVelocityFilter; }; + UserInputMapper::Input makeInput(controller::StandardButtonChannel button); + UserInputMapper::Input makeInput(controller::StandardAxisChannel axis); + UserInputMapper::Input makeInput(controller::StandardPoseChannel pose); + UserInputMapper::InputPair makePair(controller::StandardButtonChannel button, const QString& name); + UserInputMapper::InputPair makePair(controller::StandardAxisChannel button, const QString& name); + UserInputMapper::InputPair makePair(controller::StandardPoseChannel button, const QString& name); public slots: static void setLowVelocityFilter(bool newLowVelocityFilter) { _lowVelocityFilter = newLowVelocityFilter; }; protected: + int _deviceID = 0; QString _name; From db0fa6b8edfdd9571f68b21a3a176d0f76627ad7 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Fri, 16 Oct 2015 17:18:15 -0700 Subject: [PATCH 2/2] Update hydra mappings and test code --- examples/tests/controllerInterfaceTest.js | 30 +++ interface/resources/controllers/hydra.json | 28 +++ interface/resources/controllers/xbox.json | 29 +++ .../src/input-plugins/Joystick.cpp | 19 -- .../src/input-plugins/Joystick.h | 4 - .../src/input-plugins/SixenseManager.cpp | 176 +++++------------- .../src/input-plugins/SixenseManager.h | 25 +-- tests/controllers/qml/Hydra.qml | 35 ++++ tests/controllers/qml/Xbox.qml | 16 +- tests/controllers/qml/content.qml | 33 ++-- .../controllers/qml/controls/AnalogStick.qml | 5 + tests/controllers/qml/hydra/HydraButtons.qml | 18 ++ tests/controllers/qml/hydra/HydraStick.qml | 91 +++++++++ tests/controllers/qml/hydra/hydra.png | Bin 0 -> 21273 bytes tests/controllers/src/main.cpp | 12 +- 15 files changed, 321 insertions(+), 200 deletions(-) create mode 100644 examples/tests/controllerInterfaceTest.js create mode 100644 interface/resources/controllers/hydra.json create mode 100644 interface/resources/controllers/xbox.json create mode 100644 tests/controllers/qml/Hydra.qml create mode 100644 tests/controllers/qml/hydra/HydraButtons.qml create mode 100644 tests/controllers/qml/hydra/HydraStick.qml create mode 100644 tests/controllers/qml/hydra/hydra.png diff --git a/examples/tests/controllerInterfaceTest.js b/examples/tests/controllerInterfaceTest.js new file mode 100644 index 0000000000..fa8cf48b9b --- /dev/null +++ b/examples/tests/controllerInterfaceTest.js @@ -0,0 +1,30 @@ +ControllerTest = function() { + + print("Actions"); + for (var prop in Controller.Actions) { + print("\t" + prop); + } + print("Standard"); + for (var prop in Controller.Standard) { + print("\t" + prop); + } + print("Hardware"); + for (var prop in Controller.Hardware) { + print("\t" + prop); + for (var prop2 in Controller.Hardware[prop]) { + print("\t\t" + prop2); + } + } + print("Done"); + + var that = this; + Script.scriptEnding.connect(function() { + that.onCleanup(); + }); +} + +ControllerTest.prototype.onCleanup = function() { +} + + +new ControllerTest(); \ No newline at end of file diff --git a/interface/resources/controllers/hydra.json b/interface/resources/controllers/hydra.json new file mode 100644 index 0000000000..25c8db61cb --- /dev/null +++ b/interface/resources/controllers/hydra.json @@ -0,0 +1,28 @@ +{ + "name": "Hydra to Standard", + "channels": [ + { "from": "Hydra.LY", "to": "Standard.LY" }, + { "from": "Hydra.LX", "to": "Standard.LX" }, + { "from": "Hydra.LT", "to": "Standard.LT" }, + { "from": "Hydra.RY", "to": "Standard.RY" }, + { "from": "Hydra.RX", "to": "Standard.RX" }, + { "from": "Hydra.RT", "to": "Standard.RT" }, + + { "from": "Hydra.LB", "to": "Standard.LB" }, + { "from": "Hydra.LS", "to": "Standard.LS" }, + { "from": "Hydra.RB", "to": "Standard.RB" }, + { "from": "Hydra.RS", "to": "Standard.RS" }, + + { "from": "Hydra.L0", "to": "Standard.Back" }, + { "from": "Hydra.L1", "to": "Standard.DL" }, + { "from": "Hydra.L2", "to": "Standard.DD" }, + { "from": "Hydra.L3", "to": "Standard.DR" }, + { "from": "Hydra.L4", "to": "Standard.DU" }, + + { "from": "Hydra.R0", "to": "Standard.Start" }, + { "from": "Hydra.R1", "to": "Standard.X" }, + { "from": "Hydra.R2", "to": "Standard.A" }, + { "from": "Hydra.R3", "to": "Standard.B" }, + { "from": "Hydra.R4", "to": "Standard.Y" } + ] +} diff --git a/interface/resources/controllers/xbox.json b/interface/resources/controllers/xbox.json new file mode 100644 index 0000000000..bf96320707 --- /dev/null +++ b/interface/resources/controllers/xbox.json @@ -0,0 +1,29 @@ +{ + "name": "XBox to Standard", + "channels": [ + { "from": "XBox.LY", "to": "Standard.LY" }, + { "from": "XBox.LX", "to": "Standard.LX" }, + { "from": "XBox.LT", "to": "Standard.LT" }, + { "from": "XBox.LB", "to": "Standard.LB" }, + { "from": "XBox.LS", "to": "Standard.LS" }, + + { "from": "XBox.RY", "to": "Standard.RY" }, + { "from": "XBox.RX", "to": "Standard.RX" }, + { "from": "XBox.RT", "to": "Standard.RT" }, + { "from": "XBox.RB", "to": "Standard.RB" }, + { "from": "XBox.RS", "to": "Standard.RS" }, + + { "from": "XBox.Back", "to": "Standard.Back" }, + { "from": "XBox.Start", "to": "Standard.Start" }, + + { "from": "XBox.DU", "to": "Standard.DU" }, + { "from": "XBox.DD", "to": "Standard.DD" }, + { "from": "XBox.DL", "to": "Standard.DL" }, + { "from": "XBox.DR", "to": "Standard.DR" }, + + { "from": "XBox.A", "to": "Standard.A" }, + { "from": "XBox.B", "to": "Standard.B" }, + { "from": "XBox.X", "to": "Standard.X" }, + { "from": "XBox.Y", "to": "Standard.Y" } + ] +} diff --git a/libraries/input-plugins/src/input-plugins/Joystick.cpp b/libraries/input-plugins/src/input-plugins/Joystick.cpp index e7a0124deb..aa5bbbba07 100644 --- a/libraries/input-plugins/src/input-plugins/Joystick.cpp +++ b/libraries/input-plugins/src/input-plugins/Joystick.cpp @@ -143,22 +143,3 @@ void Joystick::registerToUserInputMapper(UserInputMapper& mapper) { mapper.registerDevice(_deviceID, proxy); } -void Joystick::assignDefaultInputMapping(UserInputMapper& mapper) { -#ifdef HAVE_SDL2 - const float JOYSTICK_MOVE_SPEED = 1.0f; - const float DPAD_MOVE_SPEED = 0.5f; - const float JOYSTICK_YAW_SPEED = 0.5f; - const float JOYSTICK_PITCH_SPEED = 0.25f; - const float BOOM_SPEED = 0.1f; - -#endif -} - -UserInputMapper::Input Joystick::makeInput(controller::StandardButtonChannel button) { - return UserInputMapper::Input(_deviceID, button, UserInputMapper::ChannelType::BUTTON); -} - -UserInputMapper::Input Joystick::makeInput(controller::StandardAxisChannel axis) { - return UserInputMapper::Input(_deviceID, axis, UserInputMapper::ChannelType::AXIS); -} - diff --git a/libraries/input-plugins/src/input-plugins/Joystick.h b/libraries/input-plugins/src/input-plugins/Joystick.h index c6537acafe..8e4cdb365f 100644 --- a/libraries/input-plugins/src/input-plugins/Joystick.h +++ b/libraries/input-plugins/src/input-plugins/Joystick.h @@ -37,16 +37,12 @@ public: // Device functions virtual void registerToUserInputMapper(UserInputMapper& mapper) override; - virtual void assignDefaultInputMapping(UserInputMapper& mapper) override; virtual void update(float deltaTime, bool jointsCaptured) override; virtual void focusOutEvent() override; Joystick() : InputDevice("Joystick") {} ~Joystick(); - UserInputMapper::Input makeInput(controller::StandardButtonChannel button); - UserInputMapper::Input makeInput(controller::StandardAxisChannel axis); - #ifdef HAVE_SDL2 Joystick(SDL_JoystickID instanceId, const QString& name, SDL_GameController* sdlGameController); #endif diff --git a/libraries/input-plugins/src/input-plugins/SixenseManager.cpp b/libraries/input-plugins/src/input-plugins/SixenseManager.cpp index a99e04ff13..9d3d06ecb7 100644 --- a/libraries/input-plugins/src/input-plugins/SixenseManager.cpp +++ b/libraries/input-plugins/src/input-plugins/SixenseManager.cpp @@ -123,6 +123,8 @@ void SixenseManager::activate() { loadSettings(); sixenseInit(); _activated = true; + auto userInputMapper = DependencyManager::get(); + registerToUserInputMapper(*userInputMapper); #endif } @@ -176,23 +178,13 @@ void SixenseManager::update(float deltaTime, bool jointsCaptured) { auto userInputMapper = DependencyManager::get(); if (sixenseGetNumActiveControllers() == 0) { - if (_hydrasConnected) { - qCDebug(inputplugins, "hydra disconnected"); - } - _hydrasConnected = false; - if (_deviceID != 0) { - userInputMapper->removeDevice(_deviceID); - _deviceID = 0; - _poseStateMap.clear(); - } + _poseStateMap.clear(); return; } PerformanceTimer perfTimer("sixense"); if (!_hydrasConnected) { _hydrasConnected = true; - registerToUserInputMapper(*userInputMapper); - assignDefaultInputMapping(*userInputMapper); UserActivityLogger::getInstance().connectedDevice("spatial_controller", "hydra"); } @@ -226,12 +218,15 @@ void SixenseManager::update(float deltaTime, bool jointsCaptured) { // NOTE: Sixense API returns pos data in millimeters but we IMMEDIATELY convert to meters. glm::vec3 position(data->pos[0], data->pos[1], data->pos[2]); position *= METERS_PER_MILLIMETER; - + bool left = i == 0; + using namespace controller; // Check to see if this hand/controller is on the base const float CONTROLLER_AT_BASE_DISTANCE = 0.075f; if (glm::length(position) >= CONTROLLER_AT_BASE_DISTANCE) { - handleButtonEvent(data->buttons, numActiveControllers - 1); - handleAxisEvent(data->joystick_x, data->joystick_y, data->trigger, numActiveControllers - 1); + handleButtonEvent(data->buttons, left); + _axisStateMap[left ? LX : RX] = data->joystick_x; + _axisStateMap[left ? LY : RY] = data->joystick_y; + _axisStateMap[left ? LT : RT] = data->trigger; if (!jointsCaptured) { // Rotation of Palm @@ -241,13 +236,8 @@ void SixenseManager::update(float deltaTime, bool jointsCaptured) { _poseStateMap.clear(); } } else { - _poseStateMap[(numActiveControllers - 1) == 0 ? LEFT_HAND : RIGHT_HAND] = UserInputMapper::PoseValue(); + _poseStateMap[left ? controller::StandardPoseChannel::LEFT : controller::StandardPoseChannel::RIGHT] = UserInputMapper::PoseValue(); } - -// // Read controller buttons and joystick into the hand -// palm->setControllerButtons(data->buttons); -// palm->setTrigger(data->trigger); -// palm->setJoystick(data->joystick_x, data->joystick_y); } if (numActiveControllers == 2) { @@ -367,39 +357,35 @@ void SixenseManager::focusOutEvent() { _buttonPressedMap.clear(); }; -void SixenseManager::handleAxisEvent(float stickX, float stickY, float trigger, int index) { - _axisStateMap[makeInput(AXIS_Y_POS, index).getChannel()] = (stickY > 0.0f) ? stickY : 0.0f; - _axisStateMap[makeInput(AXIS_Y_NEG, index).getChannel()] = (stickY < 0.0f) ? -stickY : 0.0f; - _axisStateMap[makeInput(AXIS_X_POS, index).getChannel()] = (stickX > 0.0f) ? stickX : 0.0f; - _axisStateMap[makeInput(AXIS_X_NEG, index).getChannel()] = (stickX < 0.0f) ? -stickX : 0.0f; - _axisStateMap[makeInput(BACK_TRIGGER, index).getChannel()] = trigger; +void SixenseManager::handleAxisEvent(float stickX, float stickY, float trigger, bool left) { } -void SixenseManager::handleButtonEvent(unsigned int buttons, int index) { +void SixenseManager::handleButtonEvent(unsigned int buttons, bool left) { + using namespace controller; if (buttons & BUTTON_0) { - _buttonPressedMap.insert(makeInput(BUTTON_0, index).getChannel()); + _buttonPressedMap.insert(left ? BACK : START); } if (buttons & BUTTON_1) { - _buttonPressedMap.insert(makeInput(BUTTON_1, index).getChannel()); + _buttonPressedMap.insert(left ? DL : X); } if (buttons & BUTTON_2) { - _buttonPressedMap.insert(makeInput(BUTTON_2, index).getChannel()); + _buttonPressedMap.insert(left ? DD : A); } if (buttons & BUTTON_3) { - _buttonPressedMap.insert(makeInput(BUTTON_3, index).getChannel()); + _buttonPressedMap.insert(left ? DR : B); } if (buttons & BUTTON_4) { - _buttonPressedMap.insert(makeInput(BUTTON_4, index).getChannel()); + _buttonPressedMap.insert(left ? DU : Y); } if (buttons & BUTTON_FWD) { - _buttonPressedMap.insert(makeInput(BUTTON_FWD, index).getChannel()); + _buttonPressedMap.insert(left ? LB : RB); } if (buttons & BUTTON_TRIGGER) { - _buttonPressedMap.insert(makeInput(BUTTON_TRIGGER, index).getChannel()); + _buttonPressedMap.insert(left ? LS : RS); } } -void SixenseManager::handlePoseEvent(glm::vec3 position, glm::quat rotation, int index) { +void SixenseManager::handlePoseEvent(glm::vec3 position, glm::quat rotation, bool left) { #ifdef HAVE_SIXENSE // From ABOVE the sixense coordinate frame looks like this: // @@ -443,7 +429,7 @@ void SixenseManager::handlePoseEvent(glm::vec3 position, glm::quat rotation, int // In addition to Qsh each hand has pre-offset introduced by the shape of the sixense controllers // and how they fit into the hand in their relaxed state. This offset is a quarter turn about // the sixense's z-axis, with its direction different for the two hands: - float sign = (index == 0) ? 1.0f : -1.0f; + float sign = left ? 1.0f : -1.0f; const glm::quat preOffset = glm::angleAxis(sign * PI / 2.0f, Vectors::UNIT_Z); // Finally, there is a post-offset (same for both hands) to get the hand's rest orientation @@ -458,104 +444,46 @@ void SixenseManager::handlePoseEvent(glm::vec3 position, glm::quat rotation, int // TODO: find a shortcut with fewer rotations. rotation = _avatarRotation * postOffset * glm::inverse(sixenseToHand) * rotation * preOffset * sixenseToHand; - _poseStateMap[makeInput(JointChannel(index)).getChannel()] = UserInputMapper::PoseValue(position, rotation); + _poseStateMap[left ? controller::StandardPoseChannel::LEFT : controller::StandardPoseChannel::RIGHT] = + UserInputMapper::PoseValue(position, rotation); #endif // HAVE_SIXENSE } void SixenseManager::registerToUserInputMapper(UserInputMapper& mapper) { // Grab the current free device ID _deviceID = mapper.getFreeDeviceID(); - auto proxy = std::make_shared(_name); proxy->getButton = [this] (const UserInputMapper::Input& input, int timestamp) -> bool { return this->getButton(input.getChannel()); }; proxy->getAxis = [this] (const UserInputMapper::Input& input, int timestamp) -> float { return this->getAxis(input.getChannel()); }; proxy->getPose = [this](const UserInputMapper::Input& input, int timestamp) -> UserInputMapper::PoseValue { return this->getPose(input.getChannel()); }; - proxy->getAvailabeInputs = [this] () -> QVector { + using namespace controller; + proxy->getAvailabeInputs = [this]() -> QVector { QVector availableInputs; - availableInputs.append(UserInputMapper::InputPair(makeInput(BUTTON_0, 0), "Left Start")); - availableInputs.append(UserInputMapper::InputPair(makeInput(BUTTON_1, 0), "Left Button 1")); - availableInputs.append(UserInputMapper::InputPair(makeInput(BUTTON_2, 0), "Left Button 2")); - availableInputs.append(UserInputMapper::InputPair(makeInput(BUTTON_3, 0), "Left Button 3")); - availableInputs.append(UserInputMapper::InputPair(makeInput(BUTTON_4, 0), "Left Button 4")); - - availableInputs.append(UserInputMapper::InputPair(makeInput(BUTTON_FWD, 0), "L1")); - availableInputs.append(UserInputMapper::InputPair(makeInput(BACK_TRIGGER, 0), "L2")); - - availableInputs.append(UserInputMapper::InputPair(makeInput(AXIS_Y_POS, 0), "Left Stick Up")); - availableInputs.append(UserInputMapper::InputPair(makeInput(AXIS_Y_NEG, 0), "Left Stick Down")); - availableInputs.append(UserInputMapper::InputPair(makeInput(AXIS_X_POS, 0), "Left Stick Right")); - availableInputs.append(UserInputMapper::InputPair(makeInput(AXIS_X_NEG, 0), "Left Stick Left")); - availableInputs.append(UserInputMapper::InputPair(makeInput(BUTTON_TRIGGER, 0), "Left Trigger Press")); - - availableInputs.append(UserInputMapper::InputPair(makeInput(BUTTON_0, 1), "Right Start")); - availableInputs.append(UserInputMapper::InputPair(makeInput(BUTTON_1, 1), "Right Button 1")); - availableInputs.append(UserInputMapper::InputPair(makeInput(BUTTON_2, 1), "Right Button 2")); - availableInputs.append(UserInputMapper::InputPair(makeInput(BUTTON_3, 1), "Right Button 3")); - availableInputs.append(UserInputMapper::InputPair(makeInput(BUTTON_4, 1), "Right Button 4")); - - availableInputs.append(UserInputMapper::InputPair(makeInput(BUTTON_FWD, 1), "R1")); - availableInputs.append(UserInputMapper::InputPair(makeInput(BACK_TRIGGER, 1), "R2")); - - availableInputs.append(UserInputMapper::InputPair(makeInput(AXIS_Y_POS, 1), "Right Stick Up")); - availableInputs.append(UserInputMapper::InputPair(makeInput(AXIS_Y_NEG, 1), "Right Stick Down")); - availableInputs.append(UserInputMapper::InputPair(makeInput(AXIS_X_POS, 1), "Right Stick Right")); - availableInputs.append(UserInputMapper::InputPair(makeInput(AXIS_X_NEG, 1), "Right Stick Left")); - availableInputs.append(UserInputMapper::InputPair(makeInput(BUTTON_TRIGGER, 1), "Right Trigger Press")); - + availableInputs.append(UserInputMapper::InputPair(makeInput(BACK), "L0")); + availableInputs.append(UserInputMapper::InputPair(makeInput(DL), "L1")); + availableInputs.append(UserInputMapper::InputPair(makeInput(DD), "L2")); + availableInputs.append(UserInputMapper::InputPair(makeInput(DR), "L3")); + availableInputs.append(UserInputMapper::InputPair(makeInput(DU), "L4")); + availableInputs.append(UserInputMapper::InputPair(makeInput(LB), "LB")); + availableInputs.append(UserInputMapper::InputPair(makeInput(LS), "LS")); + availableInputs.append(UserInputMapper::InputPair(makeInput(LX), "LX")); + availableInputs.append(UserInputMapper::InputPair(makeInput(LY), "LY")); + availableInputs.append(UserInputMapper::InputPair(makeInput(LT), "LT")); + availableInputs.append(UserInputMapper::InputPair(makeInput(START), "R0")); + availableInputs.append(UserInputMapper::InputPair(makeInput(X), "R1")); + availableInputs.append(UserInputMapper::InputPair(makeInput(A), "R2")); + availableInputs.append(UserInputMapper::InputPair(makeInput(B), "R3")); + availableInputs.append(UserInputMapper::InputPair(makeInput(Y), "R4")); + availableInputs.append(UserInputMapper::InputPair(makeInput(RB), "RB")); + availableInputs.append(UserInputMapper::InputPair(makeInput(RS), "RS")); + availableInputs.append(UserInputMapper::InputPair(makeInput(RX), "RX")); + availableInputs.append(UserInputMapper::InputPair(makeInput(RY), "RY")); + availableInputs.append(UserInputMapper::InputPair(makeInput(RT), "RT")); return availableInputs; }; - proxy->resetDeviceBindings = [this, &mapper] () -> bool { - mapper.removeAllInputChannelsForDevice(_deviceID); - this->assignDefaultInputMapping(mapper); - return true; - }; mapper.registerDevice(_deviceID, proxy); } -void SixenseManager::assignDefaultInputMapping(UserInputMapper& mapper) { - const float JOYSTICK_MOVE_SPEED = 1.0f; - const float JOYSTICK_YAW_SPEED = 0.5f; - const float JOYSTICK_PITCH_SPEED = 0.25f; - const float BUTTON_MOVE_SPEED = 1.0f; - const float BOOM_SPEED = 0.1f; - - // Left Joystick: Movement, strafing - mapper.addInputChannel(UserInputMapper::LONGITUDINAL_FORWARD, makeInput(AXIS_Y_POS, 0), JOYSTICK_MOVE_SPEED); - mapper.addInputChannel(UserInputMapper::LONGITUDINAL_BACKWARD, makeInput(AXIS_Y_NEG, 0), JOYSTICK_MOVE_SPEED); - mapper.addInputChannel(UserInputMapper::LATERAL_RIGHT, makeInput(AXIS_X_POS, 0), JOYSTICK_MOVE_SPEED); - mapper.addInputChannel(UserInputMapper::LATERAL_LEFT, makeInput(AXIS_X_NEG, 0), JOYSTICK_MOVE_SPEED); - - // Right Joystick: Camera orientation - mapper.addInputChannel(UserInputMapper::YAW_RIGHT, makeInput(AXIS_X_POS, 1), JOYSTICK_YAW_SPEED); - mapper.addInputChannel(UserInputMapper::YAW_LEFT, makeInput(AXIS_X_NEG, 1), JOYSTICK_YAW_SPEED); - mapper.addInputChannel(UserInputMapper::PITCH_UP, makeInput(AXIS_Y_POS, 1), JOYSTICK_PITCH_SPEED); - mapper.addInputChannel(UserInputMapper::PITCH_DOWN, makeInput(AXIS_Y_NEG, 1), JOYSTICK_PITCH_SPEED); - - // Buttons - mapper.addInputChannel(UserInputMapper::BOOM_IN, makeInput(BUTTON_3, 0), BOOM_SPEED); - mapper.addInputChannel(UserInputMapper::BOOM_OUT, makeInput(BUTTON_1, 0), BOOM_SPEED); - - mapper.addInputChannel(UserInputMapper::VERTICAL_UP, makeInput(BUTTON_3, 1), BUTTON_MOVE_SPEED); - mapper.addInputChannel(UserInputMapper::VERTICAL_DOWN, makeInput(BUTTON_1, 1), BUTTON_MOVE_SPEED); - - mapper.addInputChannel(UserInputMapper::SHIFT, makeInput(BUTTON_2, 0)); - mapper.addInputChannel(UserInputMapper::SHIFT, makeInput(BUTTON_2, 1)); - - mapper.addInputChannel(UserInputMapper::ACTION1, makeInput(BUTTON_4, 0)); - mapper.addInputChannel(UserInputMapper::ACTION2, makeInput(BUTTON_4, 1)); - - mapper.addInputChannel(UserInputMapper::LEFT_HAND, makeInput(LEFT_HAND)); - mapper.addInputChannel(UserInputMapper::RIGHT_HAND, makeInput(RIGHT_HAND)); - - mapper.addInputChannel(UserInputMapper::LEFT_HAND_CLICK, makeInput(BACK_TRIGGER, 0)); - mapper.addInputChannel(UserInputMapper::RIGHT_HAND_CLICK, makeInput(BACK_TRIGGER, 1)); - - // TODO find a mechanism to allow users to navigate the context menu via - mapper.addInputChannel(UserInputMapper::CONTEXT_MENU, makeInput(BUTTON_0, 0)); - mapper.addInputChannel(UserInputMapper::TOGGLE_MUTE, makeInput(BUTTON_0, 1)); - -} - // virtual void SixenseManager::saveSettings() const { Settings settings; @@ -580,15 +508,3 @@ void SixenseManager::loadSettings() { } settings.endGroup(); } - -UserInputMapper::Input SixenseManager::makeInput(unsigned int button, int index) { - return UserInputMapper::Input(_deviceID, button | (index == 0 ? LEFT_MASK : RIGHT_MASK), UserInputMapper::ChannelType::BUTTON); -} - -UserInputMapper::Input SixenseManager::makeInput(SixenseManager::JoystickAxisChannel axis, int index) { - return UserInputMapper::Input(_deviceID, axis | (index == 0 ? LEFT_MASK : RIGHT_MASK), UserInputMapper::ChannelType::AXIS); -} - -UserInputMapper::Input SixenseManager::makeInput(JointChannel joint) { - return UserInputMapper::Input(_deviceID, joint, UserInputMapper::ChannelType::POSE); -} diff --git a/libraries/input-plugins/src/input-plugins/SixenseManager.h b/libraries/input-plugins/src/input-plugins/SixenseManager.h index 4558f5d268..2e7dd3223d 100644 --- a/libraries/input-plugins/src/input-plugins/SixenseManager.h +++ b/libraries/input-plugins/src/input-plugins/SixenseManager.h @@ -25,6 +25,7 @@ #endif #include +#include #include "InputPlugin.h" @@ -44,19 +45,6 @@ const bool DEFAULT_INVERT_SIXENSE_MOUSE_BUTTONS = false; class SixenseManager : public InputPlugin, public InputDevice { Q_OBJECT public: - enum JoystickAxisChannel { - AXIS_Y_POS = 1U << 0, - AXIS_Y_NEG = 1U << 3, - AXIS_X_POS = 1U << 4, - AXIS_X_NEG = 1U << 5, - BACK_TRIGGER = 1U << 6, - }; - - enum JointChannel { - LEFT_HAND = 0, - RIGHT_HAND, - }; - SixenseManager(); // Plugin functions @@ -73,14 +61,9 @@ public: // Device functions virtual void registerToUserInputMapper(UserInputMapper& mapper) override; - virtual void assignDefaultInputMapping(UserInputMapper& mapper) override; virtual void update(float deltaTime, bool jointsCaptured) override; virtual void focusOutEvent() override; - UserInputMapper::Input makeInput(unsigned int button, int index); - UserInputMapper::Input makeInput(JoystickAxisChannel axis, int index); - UserInputMapper::Input makeInput(JointChannel joint); - virtual void saveSettings() const override; virtual void loadSettings() override; @@ -88,9 +71,9 @@ public slots: void setSixenseFilter(bool filter); private: - void handleButtonEvent(unsigned int buttons, int index); - void handleAxisEvent(float x, float y, float trigger, int index); - void handlePoseEvent(glm::vec3 position, glm::quat rotation, int index); + void handleButtonEvent(unsigned int buttons, bool left); + void handleAxisEvent(float x, float y, float trigger, bool left); + void handlePoseEvent(glm::vec3 position, glm::quat rotation, bool left); void updateCalibration(void* controllers); diff --git a/tests/controllers/qml/Hydra.qml b/tests/controllers/qml/Hydra.qml new file mode 100644 index 0000000000..5ef47c4d83 --- /dev/null +++ b/tests/controllers/qml/Hydra.qml @@ -0,0 +1,35 @@ +import QtQuick 2.1 +import QtQuick.Controls 1.0 +import QtQuick.Layouts 1.0 +import QtQuick.Dialogs 1.0 + +import "./hydra" +import "./controls" + +Item { + id: root + width: 480 + height: width * 3.0 / 4.0 + property var device + property real scale: width / 480 + property real rightOffset: (width / 2) * scale + + Image { + anchors.fill: parent + source: "hydra/hydra.png" + + HydraStick { + leftStick: true + scale: root.scale + device: root.device + } + + + HydraStick { + leftStick: false + scale: root.scale + device: root.device + } + + } +} diff --git a/tests/controllers/qml/Xbox.qml b/tests/controllers/qml/Xbox.qml index ae66081162..bc9acd5a43 100644 --- a/tests/controllers/qml/Xbox.qml +++ b/tests/controllers/qml/Xbox.qml @@ -8,14 +8,20 @@ import "./controls" Item { id: root - + property real aspect: 300.0 / 215.0 + width: 300 + height: width / aspect property var device - - property real scale: 1.0 - width: 300 * scale - height: 215 * scale + property string label: "" + property real scale: width / 300.0 Image { + Text { + anchors.left: parent.left + anchors.top: parent.top + text: root.label + visible: root.label != "" + } anchors.fill: parent source: "xbox/xbox360-controller-md.png" diff --git a/tests/controllers/qml/content.qml b/tests/controllers/qml/content.qml index 5f9bbd455e..4beda82df3 100644 --- a/tests/controllers/qml/content.qml +++ b/tests/controllers/qml/content.qml @@ -10,16 +10,24 @@ Column { id: root property var actions: Controllers.Actions property var standard: Controllers.Standard + property var hydra: null property var testMapping: null property var xbox: null Component.onCompleted: { - var patt = /^X360Controller/; + var xboxRegex = /^X360Controller/; + var hydraRegex = /^Hydra/; for (var prop in Controllers.Hardware) { - if(patt.test(prop)) { + if(xboxRegex.test(prop)) { root.xbox = Controllers.Hardware[prop] - break + print("found xbox") + continue + } + if (hydraRegex.test(prop)) { + root.hydra = Controllers.Hardware[prop] + print("found hydra") + continue } } } @@ -79,23 +87,15 @@ Column { mapping.join(standard.LB, standard.RB).to(actions.Yaw); mapping.from(actions.Yaw).clamp(0, 1).invert().to(actions.YAW_RIGHT); mapping.from(actions.Yaw).clamp(-1, 0).to(actions.YAW_LEFT); - // mapping.modifier(keyboard.Ctrl).scale(2.0) - // mapping.from(keyboard.A).to(actions.TranslateLeft) // mapping.from(keyboard.A, keyboard.Shift).to(actions.TurnLeft) // mapping.from(keyboard.A, keyboard.Shift, keyboard.Ctrl).scale(2.0).to(actions.TurnLeft) - // // First loopbacks // // Then non-loopbacks by constraint level (number of inputs) // mapping.from(xbox.RX).deadZone(0.2).to(xbox.RX) - // mapping.from(standard.RB, standard.LB, keyboard.Shift).to(actions.TurnLeft) - - // mapping.from(keyboard.A, keyboard.Shift).to(actions.TurnLeft) - - // mapping.from(keyboard.W).when(keyboard.Shift).to(actions.Forward) testMapping = mapping; enabled = false @@ -114,12 +114,19 @@ Column { } } + Row { + Xbox { device: root.standard; label: "Standard"; width: 360 } + } + Row { spacing: 8 - Xbox { device: root.xbox } - Xbox { device: root.standard } + Xbox { device: root.xbox; label: "XBox"; width: 360 } } + Row { + spacing: 8 + Hydra { device: root.hydra; width: 360 } + } Row { spacing: 8 diff --git a/tests/controllers/qml/controls/AnalogStick.qml b/tests/controllers/qml/controls/AnalogStick.qml index 5d011411c9..4e8ceb5736 100644 --- a/tests/controllers/qml/controls/AnalogStick.qml +++ b/tests/controllers/qml/controls/AnalogStick.qml @@ -8,6 +8,8 @@ Item { property int size: 64 width: size height: size + property bool invertY: false + property int halfSize: size / 2 property var controlIds: [ 0, 0 ] @@ -18,6 +20,9 @@ Item { Controllers.getValue(controlIds[0]), Controllers.getValue(controlIds[1]) ); + if (root.invertY) { + value.y = value.y * -1.0 + } canvas.requestPaint(); } diff --git a/tests/controllers/qml/hydra/HydraButtons.qml b/tests/controllers/qml/hydra/HydraButtons.qml new file mode 100644 index 0000000000..6c3070d2b1 --- /dev/null +++ b/tests/controllers/qml/hydra/HydraButtons.qml @@ -0,0 +1,18 @@ +import QtQuick 2.1 +import QtQuick.Controls 1.0 +import QtQuick.Layouts 1.0 +import QtQuick.Dialogs 1.0 + +import "./../controls" + +Item { + id: root + width: 72 * scale + height: 48 * scale + property var device + property real scale: 1.0 + property bool leftStick: true + +} + + diff --git a/tests/controllers/qml/hydra/HydraStick.qml b/tests/controllers/qml/hydra/HydraStick.qml new file mode 100644 index 0000000000..3c22789f6d --- /dev/null +++ b/tests/controllers/qml/hydra/HydraStick.qml @@ -0,0 +1,91 @@ +import QtQuick 2.1 +import QtQuick.Controls 1.0 +import QtQuick.Layouts 1.0 +import QtQuick.Dialogs 1.0 + +import "./../controls" + +Item { + id: root + property var device + property real scale: 1.0 + property bool leftStick: true + width: parent.width / 2; height: parent.height + x: leftStick ? 0 : parent.width / 2 + + Text { + x: parent.width / 2 - width / 2; y: parent.height / 2 - height / 2 + text: root.leftStick ? "L" : "R" + color: 'green' + } + + // Analog Stick + AnalogStick { + size: 64 * root.scale + x: 127 * root.scale - width / 2; y: 45 * root.scale - width / 2; z: 100 + invertY: true + controlIds: [ + root.leftStick ? root.device.LX : root.device.RX, + root.leftStick ? root.device.LY : root.device.RY + ] + } + + // Stick press + ToggleButton { + controlId: root.leftStick ? root.device.LS : root.device.RS + width: 16 * root.scale; height: 16 * root.scale + x: 127 * root.scale - width / 2; y: 45 * root.scale - width / 2; + color: 'yellow' + } + + // Trigger + AnalogButton { + controlId: root.leftStick ? root.device.LT : root.device.RT + width: 8 * root.scale ; height: 64 * root.scale + y: 24 * root.scale + x: root.leftStick ? (48 * root.scale) : root.width - (48 * root.scale) - width / 2 + } + + // Bumper + ToggleButton { + controlId: root.leftStick ? root.device.LB : root.device.RB + height: 16 * root.scale; width: 32 * root.scale + x: 128 * root.scale - width / 2; y: 24 * root.scale + color: 'red' + } + + ToggleButton { + controlId: root.leftStick ? root.device.L0 : root.device.R0 + height: 16 * root.scale; width: 4 * root.scale + x: 128 * root.scale - width / 2; y: 109 * root.scale + color: 'yellow' + } + + ToggleButton { + controlId: root.leftStick ? root.device.L1 : root.device.R1 + width: 16 * root.scale; height: 16 * root.scale + x: 103 * root.scale - width / 2; y: 100 * root.scale - height / 2 + color: 'yellow' + } + + ToggleButton { + controlId: root.leftStick ? root.device.L2 : root.device.R2 + width: 16 * root.scale; height: 16 * root.scale + x: 148 * root.scale - width / 2; y: 100 * root.scale - height / 2 + color: 'yellow' + } + + ToggleButton { + controlId: root.leftStick ? root.device.L3 : root.device.R3 + width: 16 * root.scale; height: 16 * root.scale + x: 97 * root.scale - width / 2; y: 76 * root.scale - height / 2 + color: 'yellow' + } + + ToggleButton { + controlId: root.leftStick ? root.device.L4 : root.device.R4 + width: 16 * root.scale; height: 16 * root.scale + x: 155 * root.scale - width / 2; y: 76 * root.scale - height / 2 + color: 'yellow' + } +} diff --git a/tests/controllers/qml/hydra/hydra.png b/tests/controllers/qml/hydra/hydra.png new file mode 100644 index 0000000000000000000000000000000000000000..a7549ab2312328ad5c1ca2636b1c2b1efb840795 GIT binary patch literal 21273 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 { - qDebug() << path; - } - - for (auto path : qApp->libraryPaths()) { - qDebug() << path; - } + new PluginContainerProxy(); + // Simulate our application idle loop QTimer timer; QObject::connect(&timer, &QTimer::timeout, [] { static float last = secTimestampNow(); @@ -122,11 +117,12 @@ int main(int argc, char** argv) { auto keyboardMouseDevice = static_cast(inputPlugin.data()); // TODO: this seems super hacky keyboardMouseDevice->registerToUserInputMapper(*userInputMapper); } + inputPlugin->pluginUpdate(0, false); } - //new PluginContainerProxy(); auto rootContext = engine.rootContext(); rootContext->setContextProperty("Controllers", new MyControllerScriptingInterface()); } + engine.load(getQmlDir() + "main.qml"); app.exec(); return 0;