diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index fe92972f48..a63e2f2005 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -547,6 +547,11 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : connect(&_myAvatar->getSkeletonModel(), &SkeletonModel::skeletonLoaded, this, &Application::checkSkeleton, Qt::QueuedConnection); + // Setup the userInputMapper with the actions + // Setup the keyboardMouseDevice and the user input mapper with the default bindings + _keyboardMouseDevice.registerToUserInputMapper(_userInputMapper); + _keyboardMouseDevice.assignDefaultInputMapping(_userInputMapper); + // check first run... if (_firstRun.get()) { qCDebug(interfaceapp) << "This is a first run..."; @@ -1088,6 +1093,8 @@ void Application::keyPressEvent(QKeyEvent* event) { } if (activeWindow() == _window) { + _keyboardMouseDevice.keyPressEvent(event); + bool isShifted = event->modifiers().testFlag(Qt::ShiftModifier); bool isMeta = event->modifiers().testFlag(Qt::ControlModifier); bool isOption = event->modifiers().testFlag(Qt::AltModifier); @@ -1108,11 +1115,6 @@ void Application::keyPressEvent(QKeyEvent* event) { } break; - case Qt::Key_E: - case Qt::Key_PageUp: - _myAvatar->setDriveKeys(UP, 1.0f); - break; - case Qt::Key_F: { _physicsEngine.dumpNextStats(); break; @@ -1122,16 +1124,9 @@ void Application::keyPressEvent(QKeyEvent* event) { Menu::getInstance()->triggerOption(MenuOption::Stars); break; - case Qt::Key_C: - case Qt::Key_PageDown: - _myAvatar->setDriveKeys(DOWN, 1.0f); - break; - case Qt::Key_W: if (isOption && !isShifted && !isMeta) { Menu::getInstance()->triggerOption(MenuOption::Wireframe); - } else { - _myAvatar->setDriveKeys(FWD, 1.0f); } break; @@ -1142,8 +1137,6 @@ void Application::keyPressEvent(QKeyEvent* event) { Menu::getInstance()->triggerOption(MenuOption::ScriptEditor); } else if (!isOption && !isShifted && isMeta) { takeSnapshot(); - } else { - _myAvatar->setDriveKeys(BACK, 1.0f); } break; @@ -1154,14 +1147,6 @@ void Application::keyPressEvent(QKeyEvent* event) { case Qt::Key_A: if (isShifted) { Menu::getInstance()->triggerOption(MenuOption::Atmosphere); - } else if (!isMeta) { - _myAvatar->setDriveKeys(ROT_LEFT, 1.0f); - } - break; - - case Qt::Key_D: - if (!isMeta) { - _myAvatar->setDriveKeys(ROT_RIGHT, 1.0f); } break; @@ -1176,8 +1161,6 @@ void Application::keyPressEvent(QKeyEvent* event) { } else { _raiseMirror += 0.05f; } - } else { - _myAvatar->setDriveKeys(isShifted ? UP : FWD, 1.0f); } break; @@ -1188,24 +1171,18 @@ void Application::keyPressEvent(QKeyEvent* event) { } else { _raiseMirror -= 0.05f; } - } else { - _myAvatar->setDriveKeys(isShifted ? DOWN : BACK, 1.0f); } break; case Qt::Key_Left: if (_myCamera.getMode() == CAMERA_MODE_MIRROR) { _rotateMirror += PI / 20.0f; - } else { - _myAvatar->setDriveKeys(isShifted ? LEFT : ROT_LEFT, 1.0f); } break; case Qt::Key_Right: if (_myCamera.getMode() == CAMERA_MODE_MIRROR) { _rotateMirror -= PI / 20.0f; - } else { - _myAvatar->setDriveKeys(isShifted ? RIGHT : ROT_RIGHT, 1.0f); } break; @@ -1342,65 +1319,15 @@ void Application::keyReleaseEvent(QKeyEvent* event) { _keysPressed.remove(event->key()); _controllerScriptingInterface.emitKeyReleaseEvent(event); // send events to any registered scripts - + // if one of our scripts have asked to capture this event, then stop processing it if (_controllerScriptingInterface.isKeyCaptured(event)) { return; } + _keyboardMouseDevice.keyReleaseEvent(event); switch (event->key()) { - case Qt::Key_E: - case Qt::Key_PageUp: - _myAvatar->setDriveKeys(UP, 0.0f); - break; - - case Qt::Key_C: - case Qt::Key_PageDown: - _myAvatar->setDriveKeys(DOWN, 0.0f); - break; - - case Qt::Key_W: - _myAvatar->setDriveKeys(FWD, 0.0f); - break; - - case Qt::Key_S: - _myAvatar->setDriveKeys(BACK, 0.0f); - break; - - case Qt::Key_A: - _myAvatar->setDriveKeys(ROT_LEFT, 0.0f); - break; - - case Qt::Key_D: - _myAvatar->setDriveKeys(ROT_RIGHT, 0.0f); - break; - - case Qt::Key_Up: - _myAvatar->setDriveKeys(FWD, 0.0f); - _myAvatar->setDriveKeys(UP, 0.0f); - break; - - case Qt::Key_Down: - _myAvatar->setDriveKeys(BACK, 0.0f); - _myAvatar->setDriveKeys(DOWN, 0.0f); - break; - - case Qt::Key_Left: - _myAvatar->setDriveKeys(LEFT, 0.0f); - _myAvatar->setDriveKeys(ROT_LEFT, 0.0f); - break; - - case Qt::Key_Right: - _myAvatar->setDriveKeys(RIGHT, 0.0f); - _myAvatar->setDriveKeys(ROT_RIGHT, 0.0f); - break; - case Qt::Key_Control: - case Qt::Key_Shift: - case Qt::Key_Meta: - case Qt::Key_Alt: - _myAvatar->clearDriveKeys(); - break; case Qt::Key_Space: { if (!event->isAutoRepeat()) { // this ends the HFActionEvent @@ -1426,6 +1353,8 @@ void Application::keyReleaseEvent(QKeyEvent* event) { } void Application::focusOutEvent(QFocusEvent* event) { + _keyboardMouseDevice.focusOutEvent(event); + // synthesize events for keys currently pressed, since we may not get their release events foreach (int key, _keysPressed) { QKeyEvent event(QEvent::KeyRelease, key, Qt::NoModifier); @@ -1464,6 +1393,8 @@ void Application::mouseMoveEvent(QMouseEvent* event, unsigned int deviceID) { if (_controllerScriptingInterface.isMouseCaptured()) { return; } + + _keyboardMouseDevice.mouseMoveEvent(event, deviceID); } @@ -1484,6 +1415,8 @@ void Application::mousePressEvent(QMouseEvent* event, unsigned int deviceID) { if (activeWindow() == _window) { + _keyboardMouseDevice.mousePressEvent(event); + if (event->button() == Qt::LeftButton) { _mouseDragStartedX = getTrueMouseX(); _mouseDragStartedY = getTrueMouseY(); @@ -1526,6 +1459,8 @@ void Application::mouseReleaseEvent(QMouseEvent* event, unsigned int deviceID) { } if (activeWindow() == _window) { + _keyboardMouseDevice.mouseReleaseEvent(event); + if (event->button() == Qt::LeftButton) { _mousePressed = false; @@ -1546,6 +1481,7 @@ void Application::mouseReleaseEvent(QMouseEvent* event, unsigned int deviceID) { void Application::touchUpdateEvent(QTouchEvent* event) { _altPressed = false; + if (event->type() == QEvent::TouchUpdate) { TouchEvent thisEvent(*event, _lastTouchEvent); _controllerScriptingInterface.emitTouchUpdateEvent(thisEvent); // send events to any registered scripts @@ -1557,6 +1493,8 @@ void Application::touchUpdateEvent(QTouchEvent* event) { return; } + _keyboardMouseDevice.touchUpdateEvent(event); + bool validTouch = false; if (activeWindow() == _window) { const QList& tPoints = event->touchPoints(); @@ -1593,6 +1531,8 @@ void Application::touchBeginEvent(QTouchEvent* event) { return; } + _keyboardMouseDevice.touchBeginEvent(event); + } void Application::touchEndEvent(QTouchEvent* event) { @@ -1605,6 +1545,9 @@ void Application::touchEndEvent(QTouchEvent* event) { if (_controllerScriptingInterface.isTouchCaptured()) { return; } + + _keyboardMouseDevice.touchEndEvent(event); + // put any application specific touch behavior below here.. _touchDragStartedAvgX = _touchAvgX; _touchDragStartedAvgY = _touchAvgY; @@ -1620,6 +1563,8 @@ void Application::wheelEvent(QWheelEvent* event) { if (_controllerScriptingInterface.isWheelCaptured()) { return; } + + _keyboardMouseDevice.wheelEvent(event); } void Application::dropEvent(QDropEvent *event) { @@ -2356,6 +2301,7 @@ void Application::update(float deltaTime) { updateLOD(); updateMouseRay(); // check what's under the mouse and update the mouse voxel + { PerformanceTimer perfTimer("devices"); DeviceTracker::updateAll(); @@ -2366,10 +2312,27 @@ void Application::update(float deltaTime) { SixenseManager::getInstance().update(deltaTime); JoystickScriptingInterface::getInstance().update(); } - + + _userInputMapper.update(deltaTime); + _keyboardMouseDevice.update(); + // Dispatch input events _controllerScriptingInterface.updateInputControllers(); + // Transfer the user inputs to the driveKeys + _myAvatar->clearDriveKeys(); + _myAvatar->setDriveKeys(FWD, _userInputMapper.getActionState(UserInputMapper::LONGITUDINAL_FORWARD)); + _myAvatar->setDriveKeys(BACK, _userInputMapper.getActionState(UserInputMapper::LONGITUDINAL_BACKWARD)); + _myAvatar->setDriveKeys(UP, _userInputMapper.getActionState(UserInputMapper::VERTICAL_UP)); + _myAvatar->setDriveKeys(DOWN, _userInputMapper.getActionState(UserInputMapper::VERTICAL_DOWN)); + _myAvatar->setDriveKeys(LEFT, _userInputMapper.getActionState(UserInputMapper::LATERAL_LEFT)); + _myAvatar->setDriveKeys(RIGHT, _userInputMapper.getActionState(UserInputMapper::LATERAL_RIGHT)); + _myAvatar->setDriveKeys(ROT_UP, _userInputMapper.getActionState(UserInputMapper::PITCH_UP)); + _myAvatar->setDriveKeys(ROT_DOWN, _userInputMapper.getActionState(UserInputMapper::PITCH_DOWN)); + _myAvatar->setDriveKeys(ROT_LEFT, _userInputMapper.getActionState(UserInputMapper::YAW_LEFT)); + _myAvatar->setDriveKeys(ROT_RIGHT, _userInputMapper.getActionState(UserInputMapper::YAW_RIGHT)); + + updateThreads(deltaTime); // If running non-threaded, then give the threads some time to process... DependencyManager::get()->updateOtherAvatars(deltaTime); //loop through all the other avatars and simulate them... diff --git a/interface/src/Application.h b/interface/src/Application.h index 9f87d05711..7ae96de193 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -69,6 +69,8 @@ #include "ui/ApplicationOverlay.h" #include "ui/RunningScriptsWidget.h" #include "ui/ToolWindow.h" +#include "ui/UserInputMapper.h" +#include "devices/KeyboardMouseDevice.h" #include "octree/OctreeFade.h" #include "octree/OctreePacketProcessor.h" #include "UndoStackScriptingInterface.h" @@ -517,6 +519,10 @@ private: OctreeQuery _octreeQuery; // NodeData derived class for querying octee cells from octree servers + KeyboardMouseDevice _keyboardMouseDevice; // Default input device, the good old keyboard mouse and maybe touchpad + + UserInputMapper _userInputMapper; // User input mapper allowing to mapp different real devices to the action channels that the application has to offer + MyAvatar* _myAvatar; // TODO: move this and relevant code to AvatarManager (or MyAvatar as the case may be) Camera _myCamera; // My view onto the world diff --git a/interface/src/devices/KeyboardMouseDevice.cpp b/interface/src/devices/KeyboardMouseDevice.cpp new file mode 100755 index 0000000000..94994462c3 --- /dev/null +++ b/interface/src/devices/KeyboardMouseDevice.cpp @@ -0,0 +1,253 @@ + +// +// KeyboardMouseDevice.cpp +// interface/src/devices +// +// Created by Sam Gateau on 4/27/15. +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "KeyboardMouseDevice.h" + +void KeyboardMouseDevice::update() { + _axisStateMap.clear(); + + // For touch event, we need to check that the last event is not too long ago + // Maybe it's a Qt issue, but the touch event sequence (begin, update, end) is not always called properly + // The following is a workaround to detect that the touch sequence is over in case we didn;t see the end event + if (_isTouching) { + const auto TOUCH_EVENT_MAXIMUM_WAIT = 100; //ms + auto currentTime = _clock.now(); + auto sinceLastTouch = std::chrono::duration_cast(currentTime - _lastTouchTime); + if (sinceLastTouch.count() > TOUCH_EVENT_MAXIMUM_WAIT) { + _isTouching = false; + } + } +} + +void KeyboardMouseDevice::focusOutEvent(QFocusEvent* event) { + _buttonPressedMap.clear(); +}; + +void KeyboardMouseDevice::keyPressEvent(QKeyEvent* event) { + auto input = makeInput((Qt::Key) event->key()); + auto result = _buttonPressedMap.insert(input.getChannel()); + if (!result.second) { + // key pressed again ? without catching the release event ? + } +} + +void KeyboardMouseDevice::keyReleaseEvent(QKeyEvent* event) { + auto input = makeInput((Qt::Key) event->key()); + _buttonPressedMap.erase(input.getChannel()); +} + +void KeyboardMouseDevice::mousePressEvent(QMouseEvent* event, unsigned int deviceID) { + auto input = makeInput((Qt::MouseButton) event->button()); + auto result = _buttonPressedMap.insert(input.getChannel()); + if (!result.second) { + // key pressed again ? without catching the release event ? + } + _lastCursor = event->pos(); +} + +void KeyboardMouseDevice::mouseReleaseEvent(QMouseEvent* event, unsigned int deviceID) { + auto input = makeInput((Qt::MouseButton) event->button()); + _buttonPressedMap.erase(input.getChannel()); +} + +void KeyboardMouseDevice::mouseMoveEvent(QMouseEvent* event, unsigned int deviceID) { + QPoint currentPos = event->pos(); + QPoint currentMove = currentPos - _lastCursor; + + _axisStateMap[makeInput(MOUSE_AXIS_X_POS).getChannel()] = (currentMove.x() > 0 ? currentMove.x() : 0.0f); + _axisStateMap[makeInput(MOUSE_AXIS_X_NEG).getChannel()] = (currentMove.x() < 0 ? -currentMove.x() : 0.0f); + // Y mouse is inverted positive is pointing up the screen + _axisStateMap[makeInput(MOUSE_AXIS_Y_POS).getChannel()] = (currentMove.y() < 0 ? -currentMove.y() : 0.0f); + _axisStateMap[makeInput(MOUSE_AXIS_Y_NEG).getChannel()] = (currentMove.y() > 0 ? currentMove.y() : 0.0f); + + _lastCursor = currentPos; +} + +void KeyboardMouseDevice::wheelEvent(QWheelEvent* event) { + auto currentMove = event->angleDelta() / 120.0f; + + _axisStateMap[makeInput(MOUSE_AXIS_WHEEL_X_POS).getChannel()] = (currentMove.x() > 0 ? currentMove.x() : 0.0f); + _axisStateMap[makeInput(MOUSE_AXIS_WHEEL_X_NEG).getChannel()] = (currentMove.x() < 0 ? -currentMove.x() : 0.0f); + _axisStateMap[makeInput(MOUSE_AXIS_WHEEL_Y_POS).getChannel()] = (currentMove.y() > 0 ? currentMove.y() : 0.0f); + _axisStateMap[makeInput(MOUSE_AXIS_WHEEL_Y_NEG).getChannel()] = (currentMove.y() < 0 ? -currentMove.y() : 0.0f); +} + +glm::vec2 KeyboardMouseDevice::evalAverageTouchPoints(const QList& points) const { + glm::vec2 averagePoint(0.0f); + if (points.count() > 0) { + for (auto& point : points) { + averagePoint += glm::vec2(point.pos().x(), point.pos().y()); + } + averagePoint /= (float)(points.count()); + } + return averagePoint; +} + +void KeyboardMouseDevice::touchBeginEvent(const QTouchEvent* event) { + _isTouching = event->touchPointStates().testFlag(Qt::TouchPointPressed); + _lastTouch = evalAverageTouchPoints(event->touchPoints()); + _lastTouchTime = _clock.now(); +} + +void KeyboardMouseDevice::touchEndEvent(const QTouchEvent* event) { + _isTouching = false; + _lastTouch = evalAverageTouchPoints(event->touchPoints()); + _lastTouchTime = _clock.now(); +} + +void KeyboardMouseDevice::touchUpdateEvent(const QTouchEvent* event) { + auto currentPos = evalAverageTouchPoints(event->touchPoints()); + _lastTouchTime = _clock.now(); + + if (!_isTouching) { + _isTouching = event->touchPointStates().testFlag(Qt::TouchPointPressed); + } else { + auto currentMove = currentPos - _lastTouch; + + _axisStateMap[makeInput(TOUCH_AXIS_X_POS).getChannel()] = (currentMove.x > 0 ? currentMove.x : 0.0f); + _axisStateMap[makeInput(TOUCH_AXIS_X_NEG).getChannel()] = (currentMove.x < 0 ? -currentMove.x : 0.0f); + // Y mouse is inverted positive is pointing up the screen + _axisStateMap[makeInput(TOUCH_AXIS_Y_POS).getChannel()] = (currentMove.y < 0 ? -currentMove.y : 0.0f); + _axisStateMap[makeInput(TOUCH_AXIS_Y_NEG).getChannel()] = (currentMove.y > 0 ? currentMove.y : 0.0f); + } + + _lastTouch = currentPos; +} + +UserInputMapper::Input KeyboardMouseDevice::makeInput(Qt::Key code) { + return UserInputMapper::Input(_deviceID, code & KEYBOARD_MASK, UserInputMapper::ChannelType::BUTTON); +} + +UserInputMapper::Input KeyboardMouseDevice::makeInput(Qt::MouseButton code) { + switch (code) { + case Qt::LeftButton: + return UserInputMapper::Input(_deviceID, MOUSE_BUTTON_LEFT, UserInputMapper::ChannelType::BUTTON); + case Qt::RightButton: + return UserInputMapper::Input(_deviceID, MOUSE_BUTTON_RIGHT, UserInputMapper::ChannelType::BUTTON); + case Qt::MiddleButton: + return UserInputMapper::Input(_deviceID, MOUSE_BUTTON_MIDDLE, UserInputMapper::ChannelType::BUTTON); + default: + return UserInputMapper::Input(); + }; +} + +UserInputMapper::Input KeyboardMouseDevice::makeInput(KeyboardMouseDevice::MouseAxisChannel axis) { + return UserInputMapper::Input(_deviceID, axis, UserInputMapper::ChannelType::AXIS); +} + +UserInputMapper::Input KeyboardMouseDevice::makeInput(KeyboardMouseDevice::TouchAxisChannel axis) { + return UserInputMapper::Input(_deviceID, axis, UserInputMapper::ChannelType::AXIS); +} + +UserInputMapper::Input KeyboardMouseDevice::makeInput(KeyboardMouseDevice::TouchButtonChannel button) { + return UserInputMapper::Input(_deviceID, button, UserInputMapper::ChannelType::BUTTON); +} + +void KeyboardMouseDevice::registerToUserInputMapper(UserInputMapper& mapper) { + // Grab the current free device ID + _deviceID = mapper.getFreeDeviceID(); + + auto proxy = UserInputMapper::DeviceProxy::Pointer(new UserInputMapper::DeviceProxy()); + proxy->getButton = [this] (const UserInputMapper::Input& input, int timestamp) -> bool { return this->getButton(input._channel); }; + proxy->getAxis = [this] (const UserInputMapper::Input& input, int timestamp) -> float { return this->getAxis(input._channel); }; + mapper.registerDevice(_deviceID, proxy); +} + +void KeyboardMouseDevice::assignDefaultInputMapping(UserInputMapper& mapper) { + const float BUTTON_MOVE_SPEED = 1.0f; + const float BUTTON_YAW_SPEED = 0.75f; + const float BUTTON_PITCH_SPEED = 0.5f; + const float MOUSE_YAW_SPEED = 0.5f; + const float MOUSE_PITCH_SPEED = 0.25f; + const float TOUCH_YAW_SPEED = 0.5f; + const float TOUCH_PITCH_SPEED = 0.25f; + //const float BUTTON_BOOM_SPEED = 0.1f; + + // AWSD keys mapping + mapper.addInputChannel(UserInputMapper::LONGITUDINAL_BACKWARD, makeInput(Qt::Key_S), BUTTON_MOVE_SPEED); + mapper.addInputChannel(UserInputMapper::LONGITUDINAL_FORWARD, makeInput(Qt::Key_W), BUTTON_MOVE_SPEED); + mapper.addInputChannel(UserInputMapper::LATERAL_LEFT, makeInput(Qt::Key_A), BUTTON_MOVE_SPEED); + mapper.addInputChannel(UserInputMapper::LATERAL_RIGHT, makeInput(Qt::Key_D), BUTTON_MOVE_SPEED); + mapper.addInputChannel(UserInputMapper::VERTICAL_DOWN, makeInput(Qt::Key_C), BUTTON_MOVE_SPEED); + mapper.addInputChannel(UserInputMapper::VERTICAL_UP, makeInput(Qt::Key_E), BUTTON_MOVE_SPEED); + + // mapper.addInputChannel(UserInputMapper::BOOM_IN, makeInput(Qt::Key_W), makeInput(Qt::Key_Shift), BUTTON_BOOM_SPEED); + // mapper.addInputChannel(UserInputMapper::BOOM_OUT, makeInput(Qt::Key_S), makeInput(Qt::Key_Shift), BUTTON_BOOM_SPEED); + mapper.addInputChannel(UserInputMapper::YAW_LEFT, makeInput(Qt::Key_A), makeInput(Qt::Key_Shift), BUTTON_YAW_SPEED); + mapper.addInputChannel(UserInputMapper::YAW_RIGHT, makeInput(Qt::Key_D), makeInput(Qt::Key_Shift), BUTTON_YAW_SPEED); + mapper.addInputChannel(UserInputMapper::PITCH_DOWN, makeInput(Qt::Key_C), makeInput(Qt::Key_Shift), BUTTON_PITCH_SPEED); + mapper.addInputChannel(UserInputMapper::PITCH_UP, makeInput(Qt::Key_E), makeInput(Qt::Key_Shift), BUTTON_PITCH_SPEED); + + // Arrow keys mapping + mapper.addInputChannel(UserInputMapper::LONGITUDINAL_BACKWARD, makeInput(Qt::Key_Down), BUTTON_MOVE_SPEED); + mapper.addInputChannel(UserInputMapper::LONGITUDINAL_FORWARD, makeInput(Qt::Key_Up), BUTTON_MOVE_SPEED); + mapper.addInputChannel(UserInputMapper::LATERAL_LEFT, makeInput(Qt::Key_Left), BUTTON_MOVE_SPEED); + mapper.addInputChannel(UserInputMapper::LATERAL_RIGHT, makeInput(Qt::Key_Right), BUTTON_MOVE_SPEED); + mapper.addInputChannel(UserInputMapper::VERTICAL_DOWN, makeInput(Qt::Key_PageDown), BUTTON_MOVE_SPEED); + mapper.addInputChannel(UserInputMapper::VERTICAL_UP, makeInput(Qt::Key_PageUp), BUTTON_MOVE_SPEED); + + // mapper.addInputChannel(UserInputMapper::BOOM_IN, makeInput(Qt::Key_Up), makeInput(Qt::Key_Shift), BUTTON_BOOM_SPEED); + // mapper.addInputChannel(UserInputMapper::BOOM_OUT, makeInput(Qt::Key_Down), makeInput(Qt::Key_Shift), BUTTON_BOOM_SPEED); + mapper.addInputChannel(UserInputMapper::YAW_LEFT, makeInput(Qt::Key_Left), makeInput(Qt::Key_Shift), BUTTON_YAW_SPEED); + mapper.addInputChannel(UserInputMapper::YAW_RIGHT, makeInput(Qt::Key_Right), makeInput(Qt::Key_Shift), BUTTON_YAW_SPEED); + mapper.addInputChannel(UserInputMapper::PITCH_DOWN, makeInput(Qt::Key_Down), makeInput(Qt::Key_Shift), BUTTON_PITCH_SPEED); + mapper.addInputChannel(UserInputMapper::PITCH_UP, makeInput(Qt::Key_Up), makeInput(Qt::Key_Shift), BUTTON_PITCH_SPEED); + + // Mouse move + mapper.addInputChannel(UserInputMapper::PITCH_DOWN, makeInput(MOUSE_AXIS_Y_NEG), makeInput(Qt::RightButton), MOUSE_PITCH_SPEED); + mapper.addInputChannel(UserInputMapper::PITCH_UP, makeInput(MOUSE_AXIS_Y_POS), makeInput(Qt::RightButton), MOUSE_PITCH_SPEED); + mapper.addInputChannel(UserInputMapper::YAW_LEFT, makeInput(MOUSE_AXIS_X_NEG), makeInput(Qt::RightButton), MOUSE_YAW_SPEED); + mapper.addInputChannel(UserInputMapper::YAW_RIGHT, makeInput(MOUSE_AXIS_X_POS), makeInput(Qt::RightButton), MOUSE_YAW_SPEED); + + +#ifdef Q_OS_MAC + // wheel event modifier on Mac collide with the touchpad scroll event + mapper.addInputChannel(UserInputMapper::PITCH_DOWN, makeInput(TOUCH_AXIS_Y_NEG), TOUCH_PITCH_SPEED); + mapper.addInputChannel(UserInputMapper::PITCH_UP, makeInput(TOUCH_AXIS_Y_POS), TOUCH_PITCH_SPEED); + mapper.addInputChannel(UserInputMapper::YAW_LEFT, makeInput(TOUCH_AXIS_X_NEG), TOUCH_YAW_SPEED); + mapper.addInputChannel(UserInputMapper::YAW_RIGHT, makeInput(TOUCH_AXIS_X_POS), TOUCH_YAW_SPEED); +#else + // Touch pad yaw pitch + mapper.addInputChannel(UserInputMapper::PITCH_DOWN, makeInput(TOUCH_AXIS_Y_NEG), TOUCH_PITCH_SPEED); + mapper.addInputChannel(UserInputMapper::PITCH_UP, makeInput(TOUCH_AXIS_Y_POS), TOUCH_PITCH_SPEED); + mapper.addInputChannel(UserInputMapper::YAW_LEFT, makeInput(TOUCH_AXIS_X_NEG), TOUCH_YAW_SPEED); + mapper.addInputChannel(UserInputMapper::YAW_RIGHT, makeInput(TOUCH_AXIS_X_POS), TOUCH_YAW_SPEED); + + // Wheel move + //mapper.addInputChannel(UserInputMapper::BOOM_IN, makeInput(MOUSE_AXIS_WHEEL_Y_NEG), BUTTON_BOOM_SPEED); + //mapper.addInputChannel(UserInputMapper::BOOM_OUT, makeInput(MOUSE_AXIS_WHEEL_Y_POS), BUTTON_BOOM_SPEED); + mapper.addInputChannel(UserInputMapper::LATERAL_LEFT, makeInput(MOUSE_AXIS_WHEEL_X_NEG), BUTTON_YAW_SPEED); + mapper.addInputChannel(UserInputMapper::LATERAL_RIGHT, makeInput(MOUSE_AXIS_WHEEL_X_POS), BUTTON_YAW_SPEED); + +#endif + +} + +float KeyboardMouseDevice::getButton(int channel) const { + if (!_buttonPressedMap.empty()) { + if (_buttonPressedMap.find(channel) != _buttonPressedMap.end()) { + return 1.0f; + } else { + return 0.0f; + } + } + return 0.0f; +} + +float KeyboardMouseDevice::getAxis(int channel) const { + auto axis = _axisStateMap.find(channel); + if (axis != _axisStateMap.end()) { + return (*axis).second; + } else { + return 0.0f; + } +} + diff --git a/interface/src/devices/KeyboardMouseDevice.h b/interface/src/devices/KeyboardMouseDevice.h new file mode 100755 index 0000000000..ce044b2a13 --- /dev/null +++ b/interface/src/devices/KeyboardMouseDevice.h @@ -0,0 +1,108 @@ +// +// KeyboardMouseDevice.h +// interface/src/devices +// +// Created by Sam Gateau on 4/27/15. +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_KeyboardMouseDevice_h +#define hifi_KeyboardMouseDevice_h + +#include +#include +#include "ui/UserInputMapper.h" + +class KeyboardMouseDevice { +public: + + enum KeyboardChannel { + KEYBOARD_FIRST = 0, + KEYBOARD_LAST = 255, + KEYBOARD_MASK = 0x00FF, + }; + + enum MouseButtonChannel { + MOUSE_BUTTON_LEFT = KEYBOARD_LAST + 1, + MOUSE_BUTTON_RIGHT, + MOUSE_BUTTON_MIDDLE, + }; + + enum MouseAxisChannel { + MOUSE_AXIS_X_POS = MOUSE_BUTTON_MIDDLE + 1, + MOUSE_AXIS_X_NEG, + MOUSE_AXIS_Y_POS, + MOUSE_AXIS_Y_NEG, + MOUSE_AXIS_WHEEL_Y_POS, + MOUSE_AXIS_WHEEL_Y_NEG, + MOUSE_AXIS_WHEEL_X_POS, + MOUSE_AXIS_WHEEL_X_NEG, + }; + + enum TouchAxisChannel { + TOUCH_AXIS_X_POS = MOUSE_AXIS_WHEEL_X_NEG + 1, + TOUCH_AXIS_X_NEG, + TOUCH_AXIS_Y_POS, + TOUCH_AXIS_Y_NEG, + }; + + enum TouchButtonChannel { + TOUCH_BUTTON_PRESS = TOUCH_AXIS_Y_NEG + 1, + }; + + typedef std::unordered_set ButtonPressedMap; + typedef std::map AxisStateMap; // 8 axes + + void focusOutEvent(QFocusEvent* event); + + void keyPressEvent(QKeyEvent* event); + void keyReleaseEvent(QKeyEvent* event); + + void mouseMoveEvent(QMouseEvent* event, unsigned int deviceID = 0); + void mousePressEvent(QMouseEvent* event, unsigned int deviceID = 0); + void mouseReleaseEvent(QMouseEvent* event, unsigned int deviceID = 0); + + void touchBeginEvent(const QTouchEvent* event); + void touchEndEvent(const QTouchEvent* event); + void touchUpdateEvent(const QTouchEvent* event); + + void wheelEvent(QWheelEvent* event); + + // Get current state for each channels + float getButton(int channel) const; + float getAxis(int channel) const; + + // Let's make it easy for Qt because we assume we love Qt forever + UserInputMapper::Input makeInput(Qt::Key code); + UserInputMapper::Input makeInput(Qt::MouseButton code); + UserInputMapper::Input makeInput(KeyboardMouseDevice::MouseAxisChannel axis); + UserInputMapper::Input makeInput(KeyboardMouseDevice::TouchAxisChannel axis); + UserInputMapper::Input makeInput(KeyboardMouseDevice::TouchButtonChannel button); + + KeyboardMouseDevice() {} + + void registerToUserInputMapper(UserInputMapper& mapper); + void assignDefaultInputMapping(UserInputMapper& mapper); + + // Update call MUST be called once per simulation loop + // It takes care of updating the action states and deltas + void update(); + +protected: + ButtonPressedMap _buttonPressedMap; + AxisStateMap _axisStateMap; + + int _deviceID = 0; + QPoint _lastCursor; + glm::vec2 _lastTouch; + bool _isTouching = false; + + glm::vec2 evalAverageTouchPoints(const QList& points) const; + std::chrono::high_resolution_clock _clock; + std::chrono::high_resolution_clock::time_point _lastTouchTime; +}; + +#endif // hifi_KeyboardMouseDevice_h diff --git a/interface/src/ui/UserInputMapper.cpp b/interface/src/ui/UserInputMapper.cpp new file mode 100755 index 0000000000..892ab6a9b6 --- /dev/null +++ b/interface/src/ui/UserInputMapper.cpp @@ -0,0 +1,146 @@ +// +// UserInputMapper.cpp +// interface/src/ui +// +// Created by Sam Gateau on 4/27/15. +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "UserInputMapper.h" +#include + + +// UserInputMapper Class +bool UserInputMapper::registerDevice(uint16 deviceID, const DeviceProxy::Pointer& proxy){ + _registeredDevices[deviceID] = proxy; + return true; +} + +UserInputMapper::DeviceProxy::Pointer UserInputMapper::getDeviceProxy(const Input& input) { + auto device = _registeredDevices.find(input.getDevice()); + if (device != _registeredDevices.end()) { + return (device->second); + } else { + return DeviceProxy::Pointer(); + } +} + +bool UserInputMapper::addInputChannel(Action action, const Input& input, float scale) { + return addInputChannel(action, input, Input(), scale); +} + +bool UserInputMapper::addInputChannel(Action action, const Input& input, const Input& modifier, float scale) { + // Check that the device is registered + if (!getDeviceProxy(input)) { + qDebug() << "UserInputMapper::addInputChannel: The input comes from a device #" << input.getDevice() << "is unknown. no inputChannel mapped."; + return false; + } + + auto inputChannel = InputChannel(input, modifier, action, scale); + + // Insert or replace the input to modifiers + if (inputChannel.hasModifier()) { + auto& modifiers = _inputToModifiersMap[input.getID()]; + modifiers.push_back(inputChannel._modifier); + std::sort(modifiers.begin(), modifiers.end()); + } + + // Now update the action To Inputs side of things + _actionToInputsMap.insert(ActionToInputsMap::value_type(action, inputChannel)); + + return true; +} + +int UserInputMapper::addInputChannels(const InputChannels& channels) { + int nbAdded = 0; + for (auto& channel : channels) { + nbAdded += addInputChannel(channel._action, channel._input, channel._modifier, channel._scale); + } + return nbAdded; +} + +int UserInputMapper::getInputChannels(InputChannels& channels) const { + for (auto& channel : _actionToInputsMap) { + channels.push_back(channel.second); + } + + return _actionToInputsMap.size(); +} + +void UserInputMapper::update(float deltaTime) { + + // Reset the axis state for next loop + for (auto& channel : _actionStates) { + channel = 0.0f; + } + + int currentTimestamp = 0; + + for (auto& channelInput : _actionToInputsMap) { + auto& inputMapping = channelInput.second; + auto& inputID = inputMapping._input; + bool enabled = true; + + // Check if this input channel has modifiers and collect the possibilities + auto modifiersIt = _inputToModifiersMap.find(inputID.getID()); + if (modifiersIt != _inputToModifiersMap.end()) { + Modifiers validModifiers; + bool isActiveModifier = false; + for (auto& modifier : modifiersIt->second) { + auto deviceProxy = getDeviceProxy(modifier); + if (deviceProxy->getButton(modifier, currentTimestamp)) { + validModifiers.push_back(modifier); + isActiveModifier |= (modifier.getID() == inputMapping._modifier.getID()); + } + } + enabled = (validModifiers.empty() && !inputMapping.hasModifier()) || isActiveModifier; + } + + // if enabled: default input or all modifiers on + if (enabled) { + auto deviceProxy = getDeviceProxy(inputID); + switch (inputMapping._input.getType()) { + case ChannelType::BUTTON: { + _actionStates[channelInput.first] += inputMapping._scale * float(deviceProxy->getButton(inputID, currentTimestamp));// * deltaTime; // weight the impulse by the deltaTime + break; + } + case ChannelType::AXIS: { + _actionStates[channelInput.first] += inputMapping._scale * deviceProxy->getAxis(inputID, currentTimestamp); + break; + } + case ChannelType::JOINT: { + // _channelStates[channelInput.first].jointVal = deviceProxy->getJoint(inputID, currentTimestamp); + break; + } + default: { + break; //silence please + } + } + } else{ + // Channel input not enabled + enabled = false; + } + } + + // Scale all the channel step with the scale + for (auto i = 0; i < NUM_ACTIONS; i++) { + _actionStates[i] *= _actionScales[i]; + } +} + +void UserInputMapper::assignDefaulActionScales() { + _actionScales[LONGITUDINAL_BACKWARD] = 1.0f; // 1m per unit + _actionScales[LONGITUDINAL_FORWARD] = 1.0f; // 1m per unit + _actionScales[LATERAL_LEFT] = 1.0f; // 1m per unit + _actionScales[LATERAL_RIGHT] = 1.0f; // 1m per unit + _actionScales[VERTICAL_DOWN] = 1.0f; // 1m per unit + _actionScales[VERTICAL_UP] = 1.0f; // 1m per unit + _actionScales[YAW_LEFT] = 1.0f; // 1 degree per unit + _actionScales[YAW_RIGHT] = 1.0f; // 1 degree per unit + _actionScales[PITCH_DOWN] = 1.0f; // 1 degree per unit + _actionScales[PITCH_UP] = 1.0f; // 1 degree per unit + _actionScales[BOOM_IN] = 1.0f; // 1m per unit + _actionScales[BOOM_OUT] = 1.0f; // 1m per unit +} diff --git a/interface/src/ui/UserInputMapper.h b/interface/src/ui/UserInputMapper.h new file mode 100755 index 0000000000..32a1782419 --- /dev/null +++ b/interface/src/ui/UserInputMapper.h @@ -0,0 +1,178 @@ +// +// UserInputMapper.h +// interface/src/ui +// +// Created by Sam Gateau on 4/27/15. +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_UserInputMapper_h +#define hifi_UserInputMapper_h + +#include +#include + +#include +#include +#include + +class UserInputMapper : public QObject { + Q_OBJECT +public: + typedef unsigned short uint16; + typedef unsigned int uint32; + + enum class ChannelType { + UNKNOWN = 0, + BUTTON = 1, + AXIS, + JOINT, + }; + + // Input is the unique identifier to find a n input channel of a particular device + // Devices are responsible for registering to the UseInputMapper so their input channels can be sued and mapped + // to the Action channels + class Input { + public: + union { + struct { + uint16 _device; // Up to 64K possible devices + uint16 _channel : 14; // 2^14 possible channel per Device + uint16 _type : 2; // 2 bits to store the Type directly in the ID + }; + uint32 _id = 0; // by default Input is 0 meaning invalid + }; + + bool isValid() const { return (_id != 0); } + + uint16 getDevice() const { return _device; } + uint16 getChannel() const { return _channel; } + uint32 getID() const { return _id; } + + ChannelType getType() const { return (ChannelType) _type; } + bool isButton() const { return getType() == ChannelType::BUTTON; } + bool isAxis() const { return getType() == ChannelType::AXIS; } + bool isJoint() const { return getType() == ChannelType::JOINT; } + + explicit Input() {} + explicit Input(uint32 id) : _id(id) {} + explicit Input(uint16 device, uint16 channel, ChannelType type) : _device(device), _channel(channel), _type(uint16(type)) {} + Input(const Input& src) : _id(src._id) {} + Input& operator = (const Input& src) { _id = src._id; return (*this); } + bool operator < (const Input& src) const { return _id < src._id; } + }; + + // Modifiers are just button inputID + typedef std::vector< Input > Modifiers; + + class JointValue { + public: + glm::vec3 translation{ 0.0f }; + glm::quat rotation; + + JointValue() {}; + JointValue(const JointValue&) = default; + JointValue& operator = (const JointValue&) = default; + }; + + typedef std::function ButtonGetter; + typedef std::function AxisGetter; + typedef std::function JointGetter; + + class DeviceProxy { + public: + DeviceProxy() {} + + ButtonGetter getButton = [] (const Input& input, int timestamp) -> bool { return false; }; + AxisGetter getAxis = [] (const Input& input, int timestamp) -> bool { return 0.0f; }; + JointGetter getJoint = [] (const Input& input, int timestamp) -> JointValue { return JointValue(); }; + + typedef std::shared_ptr Pointer; + }; + // GetFreeDeviceID should be called before registering a device to use an ID not used by a different device. + uint16 getFreeDeviceID() { return _nextFreeDeviceID++; } + bool registerDevice(uint16 deviceID, const DeviceProxy::Pointer& device); + DeviceProxy::Pointer getDeviceProxy(const Input& input); + + + // Actions are the output channels of the Mapper, that's what the InputChannel map to + // For now the Actions are hardcoded, this is bad, but we will fix that in the near future + enum Action { + LONGITUDINAL_BACKWARD = 0, + LONGITUDINAL_FORWARD, + + LATERAL_LEFT, + LATERAL_RIGHT, + + VERTICAL_DOWN, + VERTICAL_UP, + + YAW_LEFT, + YAW_RIGHT, + + PITCH_DOWN, + PITCH_UP, + + BOOM_IN, + BOOM_OUT, + + NUM_ACTIONS, + }; + + float getActionState(Action action) const { return _actionStates[action]; } + void assignDefaulActionScales(); + + // Add input channel to the mapper and check that all the used channels are registered. + // Return true if theinput channel is created correctly, false either + bool addInputChannel(Action action, const Input& input, float scale = 1.0f); + bool addInputChannel(Action action, const Input& input, const Input& modifer, float scale = 1.0f); + + // Under the hood, the input channels are organized in map sorted on the _output + // The InputChannel class is just the full values describing the input channel in one object + class InputChannel { + public: + Input _input; + Input _modifier = Input(); // make it invalid by default, meaning no modifier + Action _action = LONGITUDINAL_BACKWARD; + float _scale = 0.0f; + + InputChannel() {} + InputChannel(const Input& input, const Input& modifier, Action action, float scale = 1.0f) : + _input(input), _modifier(modifier), _action(action), _scale(scale) {} + InputChannel(const InputChannel& src) : InputChannel(src._input, src._modifier, src._action, src._scale) {} + InputChannel& operator = (const InputChannel& src) { _input = src._input; _modifier = src._modifier; _action = src._action; _scale = src._scale; return (*this); } + + bool hasModifier() { return _modifier.isValid(); } + }; + typedef std::vector< InputChannel > InputChannels; + + // Add a bunch of input channels, return the true number of channels that successfully were added + int addInputChannels(const InputChannels& channels); + //Grab all the input channels currently in use, return the number + int getInputChannels(InputChannels& channels) const; + + // Update means go grab all the device input channels and update the output channel values + void update(float deltaTime); + + // Default contruct allocate the poutput size with the current hardcoded action channels + UserInputMapper() { assignDefaulActionScales(); } + +protected: + typedef std::map DevicesMap; + DevicesMap _registeredDevices; + uint16 _nextFreeDeviceID = 1; + + typedef std::map InputToMoModifiersMap; + InputToMoModifiersMap _inputToModifiersMap; + + typedef std::multimap ActionToInputsMap; + ActionToInputsMap _actionToInputsMap; + + std::vector _actionStates = std::vector(NUM_ACTIONS, 0.0f); + std::vector _actionScales = std::vector(NUM_ACTIONS, 1.0f); +}; + +#endif // hifi_UserInputMapper_h diff --git a/libraries/script-engine/src/TouchEvent.h b/libraries/script-engine/src/TouchEvent.h index 9c1147fecf..d9eedf50d0 100644 --- a/libraries/script-engine/src/TouchEvent.h +++ b/libraries/script-engine/src/TouchEvent.h @@ -13,6 +13,7 @@ #define hifi_TouchEvent_h #include +#include class TouchEvent { public: