diff --git a/examples/tests/dot.png b/examples/tests/dot.png new file mode 100644 index 0000000000..4ccfdc80df Binary files /dev/null and b/examples/tests/dot.png differ diff --git a/examples/tests/overlayMouseTrackingTest.js b/examples/tests/overlayMouseTrackingTest.js new file mode 100644 index 0000000000..d18fcfb599 --- /dev/null +++ b/examples/tests/overlayMouseTrackingTest.js @@ -0,0 +1,46 @@ +MouseTracker = function() { + this.WIDTH = 60; + this.HEIGHT = 60; + + this.overlay = Overlays.addOverlay("image", { + imageURL: Script.resolvePath("dot.png"), + width: this.HEIGHT, + height: this.WIDTH, + x: 100, + y: 100, + visible: true + }); + + var that = this; + Script.scriptEnding.connect(function() { + that.onCleanup(); + }); + + Controller.mousePressEvent.connect(function(event) { + that.onMousePress(event); + }); + + Controller.mouseMoveEvent.connect(function(event) { + that.onMouseMove(event); + }); +} + +MouseTracker.prototype.onCleanup = function() { + Overlays.deleteOverlay(this.overlay); +} + +MouseTracker.prototype.onMousePress = function(event) { +} + +MouseTracker.prototype.onMouseMove = function(event) { + var width = Overlays.width(); + var height = Overlays.height(); + var x = Math.max(event.x, 0); + x = Math.min(x, width); + var y = Math.max(event.y, 0); + y = Math.min(y, height); + Overlays.editOverlay(this.overlay, {x: x - this.WIDTH / 2.0, y: y - this.HEIGHT / 2.0}); +} + + +new MouseTracker(); \ No newline at end of file diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 2a8f7cf8b5..5f127988e9 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1849,8 +1849,16 @@ void Application::mouseMoveEvent(QMouseEvent* event, unsigned int deviceID) { _entities.mouseMoveEvent(event, deviceID); + { + auto offscreenUi = DependencyManager::get(); + QPointF transformedPos = offscreenUi->mapToVirtualScreen(event->localPos(), _glWidget); + QMouseEvent mappedEvent(event->type(), + transformedPos, + event->screenPos(), event->button(), + event->buttons(), event->modifiers()); + _controllerScriptingInterface.emitMouseMoveEvent(&mappedEvent, deviceID); // send events to any registered scripts + } - _controllerScriptingInterface.emitMouseMoveEvent(event, deviceID); // send events to any registered scripts // if one of our scripts have asked to capture this event, then stop processing it if (_controllerScriptingInterface.isMouseCaptured()) { return; @@ -1865,12 +1873,19 @@ void Application::mouseMoveEvent(QMouseEvent* event, unsigned int deviceID) { void Application::mousePressEvent(QMouseEvent* event, unsigned int deviceID) { // Inhibit the menu if the user is using alt-mouse dragging _altPressed = false; - if (!_aboutToQuit) { _entities.mousePressEvent(event, deviceID); } - _controllerScriptingInterface.emitMousePressEvent(event); // send events to any registered scripts + { + auto offscreenUi = DependencyManager::get(); + QPointF transformedPos = offscreenUi->mapToVirtualScreen(event->localPos(), _glWidget); + QMouseEvent mappedEvent(event->type(), + transformedPos, + event->screenPos(), event->button(), + event->buttons(), event->modifiers()); + _controllerScriptingInterface.emitMousePressEvent(&mappedEvent); // send events to any registered scripts + } // if one of our scripts have asked to capture this event, then stop processing it if (_controllerScriptingInterface.isMouseCaptured()) { @@ -1921,7 +1936,15 @@ void Application::mouseReleaseEvent(QMouseEvent* event, unsigned int deviceID) { _entities.mouseReleaseEvent(event, deviceID); } - _controllerScriptingInterface.emitMouseReleaseEvent(event); // send events to any registered scripts + { + auto offscreenUi = DependencyManager::get(); + QPointF transformedPos = offscreenUi->mapToVirtualScreen(event->localPos(), _glWidget); + QMouseEvent mappedEvent(event->type(), + transformedPos, + event->screenPos(), event->button(), + event->buttons(), event->modifiers()); + _controllerScriptingInterface.emitMouseReleaseEvent(&mappedEvent); // send events to any registered scripts + } // if one of our scripts have asked to capture this event, then stop processing it if (_controllerScriptingInterface.isMouseCaptured()) { diff --git a/interface/src/ui/overlays/Overlays.cpp b/interface/src/ui/overlays/Overlays.cpp index bc786f3f4c..db3360cbbf 100644 --- a/interface/src/ui/overlays/Overlays.cpp +++ b/interface/src/ui/overlays/Overlays.cpp @@ -10,7 +10,7 @@ #include "Overlays.h" -#include +#include #include @@ -31,6 +31,7 @@ #include "TextOverlay.h" #include "Text3DOverlay.h" #include "Web3DOverlay.h" +#include Overlays::Overlays() : _nextOverlayID(1) { @@ -331,10 +332,6 @@ void Overlays::setParentPanel(unsigned int childId, unsigned int panelId) { unsigned int Overlays::getOverlayAtPoint(const glm::vec2& point) { glm::vec2 pointCopy = point; - if (qApp->isHMDMode()) { - pointCopy = qApp->getApplicationCompositor().screenToOverlay(point); - } - QReadLocker lock(&_lock); if (!_enabled) { return 0; @@ -607,3 +604,13 @@ void Overlays::deletePanel(unsigned int panelId) { bool Overlays::isAddedOverlay(unsigned int id) { return _overlaysHUD.contains(id) || _overlaysWorld.contains(id); } + +float Overlays::width() const { + auto offscreenUi = DependencyManager::get(); + return offscreenUi->getWindow()->size().width(); +} + +float Overlays::height() const { + auto offscreenUi = DependencyManager::get(); + return offscreenUi->getWindow()->size().height(); +} \ No newline at end of file diff --git a/interface/src/ui/overlays/Overlays.h b/interface/src/ui/overlays/Overlays.h index 5bd8098150..62f00b8989 100644 --- a/interface/src/ui/overlays/Overlays.h +++ b/interface/src/ui/overlays/Overlays.h @@ -113,6 +113,10 @@ public slots: /// overlay; in meters if it is a 3D text overlay QSizeF textSize(unsigned int id, const QString& text) const; + // Return the size of the virtual screen + float width() const; + float height() const; + /// adds a panel that has already been created unsigned int addPanel(OverlayPanel::Pointer panel); diff --git a/libraries/input-plugins/src/input-plugins/SixenseManager.cpp b/libraries/input-plugins/src/input-plugins/SixenseManager.cpp index 9bc3a71fd5..aa04c49adb 100644 --- a/libraries/input-plugins/src/input-plugins/SixenseManager.cpp +++ b/libraries/input-plugins/src/input-plugins/SixenseManager.cpp @@ -13,11 +13,12 @@ #include -#include +#include #include +#include +#include #include "NumericalConstants.h" -#include #include "SixenseManager.h" #include "UserActivityLogger.h" @@ -38,14 +39,9 @@ const unsigned int RIGHT_MASK = 1U << 1; const int CALIBRATION_STATE_IDLE = 0; const int CALIBRATION_STATE_X = 1; -const int CALIBRATION_STATE_Y = 2; -const int CALIBRATION_STATE_Z = 3; -const int CALIBRATION_STATE_COMPLETE = 4; +const int CALIBRATION_STATE_COMPLETE = 2; -// default (expected) location of neck in sixense space -const float NECK_X = 0.25f; // meters -const float NECK_Y = 0.3f; // meters -const float NECK_Z = 0.3f; // meters +const glm::vec3 DEFAULT_AVATAR_POSITION(-0.25f, -0.35f, -0.3f); // in hydra frame const float CONTROLLER_THRESHOLD = 0.35f; @@ -92,9 +88,7 @@ bool SixenseManager::isSupported() const { void SixenseManager::activate() { #ifdef HAVE_SIXENSE _calibrationState = CALIBRATION_STATE_IDLE; - // By default we assume the _neckBase (in orb frame) is as high above the orb - // as the "torso" is below it. - _neckBase = glm::vec3(NECK_X, -NECK_Y, NECK_Z); + _avatarPosition = DEFAULT_AVATAR_POSITION; CONTAINER->addMenu(MENU_PATH); CONTAINER->addMenuItem(MENU_PATH, TOGGLE_SMOOTH, @@ -258,11 +252,13 @@ void SixenseManager::update(float deltaTime, bool jointsCaptured) { #ifdef HAVE_SIXENSE // the calibration sequence is: -// (1) press BUTTON_FWD on both hands -// (2) reach arm straight out to the side (X) -// (3) lift arms staight up above head (Y) -// (4) move arms a bit forward (Z) -// (5) release BUTTON_FWD on both hands +// (1) reach arm straight out to the sides (xAxis is to the left) +// (2) press BUTTON_FWD on both hands and hold for one second +// (3) release both BUTTON_FWDs +// +// The code will: +// (4) assume that the orb is on a flat surface (yAxis is UP) +// (5) compute the forward direction (zAxis = xAxis cross yAxis) const float MINIMUM_ARM_REACH = 0.3f; // meters const float MAXIMUM_NOISE_LEVEL = 0.05f; // meters @@ -279,21 +275,16 @@ void SixenseManager::updateCalibration(void* controllersX) { return; } switch (_calibrationState) { - case CALIBRATION_STATE_Y: - case CALIBRATION_STATE_Z: case CALIBRATION_STATE_COMPLETE: { // compute calibration results - // ATM we only handle the case where the XAxis has been measured, and we assume the rest - // (i.e. that the orb is on a level surface) - // TODO: handle COMPLETE state where all three axes have been defined. This would allow us - // to also handle the case where left and right controllers have been reversed. - _neckBase = 0.5f * (_reachLeft + _reachRight); // neck is midway between right and left reaches + _avatarPosition = - 0.5f * (_reachLeft + _reachRight); // neck is midway between right and left hands glm::vec3 xAxis = glm::normalize(_reachRight - _reachLeft); - glm::vec3 yAxis(0.0f, 1.0f, 0.0f); - glm::vec3 zAxis = glm::normalize(glm::cross(xAxis, yAxis)); - xAxis = glm::normalize(glm::cross(yAxis, zAxis)); - _orbRotation = glm::inverse(glm::quat_cast(glm::mat3(xAxis, yAxis, zAxis))); + glm::vec3 zAxis = glm::normalize(glm::cross(xAxis, Vectors::UNIT_Y)); + xAxis = glm::normalize(glm::cross(Vectors::UNIT_Y, zAxis)); + _avatarRotation = glm::inverse(glm::quat_cast(glm::mat3(xAxis, Vectors::UNIT_Y, zAxis))); + const float Y_OFFSET_CALIBRATED_HANDS_TO_AVATAR = -0.3f; + _avatarPosition.y += Y_OFFSET_CALIBRATED_HANDS_TO_AVATAR; qCDebug(inputplugins, "succeess: sixense calibration"); } break; @@ -349,54 +340,10 @@ void SixenseManager::updateCalibration(void* controllersX) { _lockExpiry = now + LOCK_DURATION; _lastDistance = 0.0f; _reachUp = 0.5f * (_reachLeft + _reachRight); - _calibrationState = CALIBRATION_STATE_Y; + _calibrationState = CALIBRATION_STATE_COMPLETE; qCDebug(inputplugins, "success: sixense calibration: left"); } } - else if (_calibrationState == CALIBRATION_STATE_Y) { - glm::vec3 torso = 0.5f * (_reachLeft + _reachRight); - glm::vec3 averagePosition = 0.5f * (_averageLeft + _averageRight); - float distance = (averagePosition - torso).y; - if (fabsf(distance) > fabsf(_lastDistance) + MAXIMUM_NOISE_LEVEL) { - // distance is increasing so acquire the data and push the expiry out - _reachUp = averagePosition; - _lastDistance = distance; - _lockExpiry = now + LOCK_DURATION; - } else if (now > _lockExpiry) { - if (_lastDistance > MINIMUM_ARM_REACH) { - // lock has expired so clamp the data and move on - _reachForward = _reachUp; - _lastDistance = 0.0f; - _lockExpiry = now + LOCK_DURATION; - _calibrationState = CALIBRATION_STATE_Z; - qCDebug(inputplugins, "success: sixense calibration: up"); - } - } - } - else if (_calibrationState == CALIBRATION_STATE_Z) { - glm::vec3 xAxis = glm::normalize(_reachRight - _reachLeft); - glm::vec3 torso = 0.5f * (_reachLeft + _reachRight); - //glm::vec3 yAxis = glm::normalize(_reachUp - torso); - glm::vec3 yAxis(0.0f, 1.0f, 0.0f); - glm::vec3 zAxis = glm::normalize(glm::cross(xAxis, yAxis)); - - glm::vec3 averagePosition = 0.5f * (_averageLeft + _averageRight); - float distance = glm::dot((averagePosition - torso), zAxis); - if (fabs(distance) > fabs(_lastDistance)) { - // distance is increasing so acquire the data and push the expiry out - _reachForward = averagePosition; - _lastDistance = distance; - _lockExpiry = now + LOCK_DURATION; - } else if (now > _lockExpiry) { - if (fabsf(_lastDistance) > 0.05f * MINIMUM_ARM_REACH) { - // lock has expired so clamp the data and move on - _calibrationState = CALIBRATION_STATE_COMPLETE; - qCDebug(inputplugins, "success: sixense calibration: forward"); - // TODO: it is theoretically possible to detect that the controllers have been - // accidentally switched (left hand is holding right controller) and to swap the order. - } - } - } } #endif // HAVE_SIXENSE @@ -456,12 +403,9 @@ void SixenseManager::handlePoseEvent(glm::vec3 position, glm::quat rotation, int // z // Transform the measured position into body frame. - glm::vec3 neck = _neckBase; - // Set y component of the "neck" to raise the measured position a little bit. - neck.y = 0.5f; - position = _orbRotation * (position - neck); + position = _avatarRotation * (position + _avatarPosition); - // From ABOVE the hand canonical axes looks like this: + // From ABOVE the hand canonical axes look like this: // // | | | | y | | | | // | | | | | | | | | @@ -480,28 +424,25 @@ void SixenseManager::handlePoseEvent(glm::vec3 position, glm::quat rotation, int // // Qsh = angleAxis(PI, zAxis) * angleAxis(-PI/2, xAxis) // - const glm::vec3 xAxis = glm::vec3(1.0f, 0.0f, 0.0f); - const glm::vec3 yAxis = glm::vec3(0.0f, 1.0f, 0.0f); - const glm::vec3 zAxis = glm::vec3(0.0f, 0.0f, 1.0f); - const glm::quat sixenseToHand = glm::angleAxis(PI, zAxis) * glm::angleAxis(-PI/2.0f, xAxis); + const glm::quat sixenseToHand = glm::angleAxis(PI, Vectors::UNIT_Z) * glm::angleAxis(-PI/2.0f, Vectors::UNIT_X); // 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; - const glm::quat preOffset = glm::angleAxis(sign * PI / 2.0f, zAxis); + 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 // (fingers forward, palm down) aligned properly in the avatar's model-frame, // and then a flip about the yAxis to get into model-frame. - const glm::quat postOffset = glm::angleAxis(PI, yAxis) * glm::angleAxis(PI / 2.0f, xAxis); + const glm::quat postOffset = glm::angleAxis(PI, Vectors::UNIT_Y) * glm::angleAxis(PI / 2.0f, Vectors::UNIT_X); // The total rotation of the hand uses the formula: // // rotation = postOffset * Qsh^ * (measuredRotation * preOffset) * Qsh // // TODO: find a shortcut with fewer rotations. - rotation = postOffset * glm::inverse(sixenseToHand) * rotation * preOffset * sixenseToHand; + rotation = _avatarRotation * postOffset * glm::inverse(sixenseToHand) * rotation * preOffset * sixenseToHand; _poseStateMap[makeInput(JointChannel(index)).getChannel()] = UserInputMapper::PoseValue(position, rotation); #endif // HAVE_SIXENSE diff --git a/libraries/input-plugins/src/input-plugins/SixenseManager.h b/libraries/input-plugins/src/input-plugins/SixenseManager.h index 03482287d9..22340cfc95 100644 --- a/libraries/input-plugins/src/input-plugins/SixenseManager.h +++ b/libraries/input-plugins/src/input-plugins/SixenseManager.h @@ -97,8 +97,8 @@ private: int _calibrationState; // these are calibration results - glm::vec3 _neckBase; // midpoint between controllers during X-axis calibration - glm::quat _orbRotation; // rotates from orb frame into body frame + glm::vec3 _avatarPosition; // in hydra-frame + glm::quat _avatarRotation; // in hydra-frame float _armLength; // these are measured values used to compute the calibration results diff --git a/libraries/render-utils/src/OffscreenQmlSurface.cpp b/libraries/render-utils/src/OffscreenQmlSurface.cpp index 9923849aab..3f940d8569 100644 --- a/libraries/render-utils/src/OffscreenQmlSurface.cpp +++ b/libraries/render-utils/src/OffscreenQmlSurface.cpp @@ -496,6 +496,13 @@ QPointF OffscreenQmlSurface::mapWindowToUi(const QPointF& sourcePosition, QObjec return QPointF(offscreenPosition.x, offscreenPosition.y); } +QPointF OffscreenQmlSurface::mapToVirtualScreen(const QPointF& originalPoint, QObject* originalWidget) { + QPointF transformedPos = _mouseTranslator(originalPoint); + transformedPos = mapWindowToUi(transformedPos, originalWidget); + return transformedPos; +} + + /////////////////////////////////////////////////////// // // Event handling customization @@ -541,8 +548,9 @@ bool OffscreenQmlSurface::eventFilter(QObject* originalDestination, QEvent* even case QEvent::Wheel: { QWheelEvent* wheelEvent = static_cast(event); + QPointF transformedPos = mapToVirtualScreen(wheelEvent->pos(), originalDestination); QWheelEvent mappedEvent( - mapWindowToUi(wheelEvent->pos(), originalDestination), + transformedPos, wheelEvent->delta(), wheelEvent->buttons(), wheelEvent->modifiers(), wheelEvent->orientation()); mappedEvent.ignore(); @@ -558,9 +566,7 @@ bool OffscreenQmlSurface::eventFilter(QObject* originalDestination, QEvent* even case QEvent::MouseButtonRelease: case QEvent::MouseMove: { QMouseEvent* mouseEvent = static_cast(event); - QPointF originalPos = mouseEvent->localPos(); - QPointF transformedPos = _mouseTranslator(originalPos); - transformedPos = mapWindowToUi(transformedPos, originalDestination); + QPointF transformedPos = mapToVirtualScreen(mouseEvent->localPos(), originalDestination); QMouseEvent mappedEvent(mouseEvent->type(), transformedPos, mouseEvent->screenPos(), mouseEvent->button(), diff --git a/libraries/render-utils/src/OffscreenQmlSurface.h b/libraries/render-utils/src/OffscreenQmlSurface.h index 13e467bccd..01dd2b88f9 100644 --- a/libraries/render-utils/src/OffscreenQmlSurface.h +++ b/libraries/render-utils/src/OffscreenQmlSurface.h @@ -61,6 +61,7 @@ public: QQuickWindow* getWindow(); QObject* getEventHandler(); + QPointF mapToVirtualScreen(const QPointF& originalPoint, QObject* originalWidget); virtual bool eventFilter(QObject* originalDestination, QEvent* event); signals: diff --git a/libraries/shared/src/Interpolate.cpp b/libraries/shared/src/Interpolate.cpp index 7acc0c8cbd..bef69c9a33 100644 --- a/libraries/shared/src/Interpolate.cpp +++ b/libraries/shared/src/Interpolate.cpp @@ -23,12 +23,12 @@ float Interpolate::bezierInterpolate(float y1, float y2, float y3, float u) { float Interpolate::interpolate3Points(float y1, float y2, float y3, float u) { assert(0.0f <= u && u <= 1.0f); - if (u <= 0.5f && y1 == y2 || u >= 0.5f && y2 == y3) { + if ((u <= 0.5f && y1 == y2) || (u >= 0.5f && y2 == y3)) { // Flat line. return y2; } - if (y2 >= y1 && y2 >= y3 || y2 <= y1 && y2 <= y3) { + if ((y2 >= y1 && y2 >= y3) || (y2 <= y1 && y2 <= y3)) { // U or inverted-U shape. // Make the slope at y2 = 0, which means that the control points half way between the value points have the value y2. if (u <= 0.5f) {