diff --git a/interface/resources/icons/tablet-icons/raise-hand-a.svg b/interface/resources/icons/tablet-icons/raise-hand-a.svg new file mode 100644 index 0000000000..fd35073332 --- /dev/null +++ b/interface/resources/icons/tablet-icons/raise-hand-a.svg @@ -0,0 +1,70 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/interface/resources/icons/tablet-icons/raise-hand-i.svg b/interface/resources/icons/tablet-icons/raise-hand-i.svg new file mode 100644 index 0000000000..50a6aa2606 --- /dev/null +++ b/interface/resources/icons/tablet-icons/raise-hand-i.svg @@ -0,0 +1,60 @@ + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/interface/resources/qml/hifi/Feed.qml b/interface/resources/qml/hifi/Feed.qml index fc108f47e3..c1bd35f49d 100644 --- a/interface/resources/qml/hifi/Feed.qml +++ b/interface/resources/qml/hifi/Feed.qml @@ -238,8 +238,25 @@ Column { stackShadowNarrowing: root.stackShadowNarrowing; shadowHeight: root.stackedCardShadowHeight; - hoverThunk: function () { scroll.currentIndex = index; } - unhoverThunk: function () { scroll.currentIndex = -1; } + hoverThunk: function () { scrollToIndex(index); } + unhoverThunk: function () { scrollToIndex(-1); } } } + NumberAnimation { + id: anim; + target: scroll; + property: "contentX"; + duration: 250; + } + function scrollToIndex(index) { + anim.running = false; + var pos = scroll.contentX; + var destPos; + scroll.positionViewAtIndex(index, ListView.Contain); + destPos = scroll.contentX; + anim.from = pos; + anim.to = destPos; + scroll.currentIndex = index; + anim.running = true; + } } diff --git a/interface/src/ui/overlays/Overlays.cpp b/interface/src/ui/overlays/Overlays.cpp index 61a283b88c..4970112405 100644 --- a/interface/src/ui/overlays/Overlays.cpp +++ b/interface/src/ui/overlays/Overlays.cpp @@ -408,6 +408,7 @@ RayToOverlayIntersectionResult Overlays::findRayIntersectionInternal(const PickR const QVector& overlaysToInclude, const QVector& overlaysToDiscard, bool visibleOnly, bool collidableOnly) { + QReadLocker lock(&_lock); float bestDistance = std::numeric_limits::max(); bool bestIsFront = false; diff --git a/plugins/openvr/src/ViveControllerManager.cpp b/plugins/openvr/src/ViveControllerManager.cpp index a5b742c32c..ccc843b5cc 100644 --- a/plugins/openvr/src/ViveControllerManager.cpp +++ b/plugins/openvr/src/ViveControllerManager.cpp @@ -63,7 +63,7 @@ static glm::mat4 computeOffset(glm::mat4 defaultToReferenceMat, glm::mat4 defaul } static bool sortPucksYPosition(std::pair firstPuck, std::pair secondPuck) { - return (firstPuck.second.translation.y < firstPuck.second.translation.y); + return (firstPuck.second.translation.y < secondPuck.second.translation.y); } static QString deviceTrackingResultToString(vr::ETrackingResult trackingResult) { @@ -280,6 +280,7 @@ void ViveControllerManager::InputDevice::calibrateOrUncalibrate(const controller } void ViveControllerManager::InputDevice::calibrate(const controller::InputCalibrationData& inputCalibration) { + qDebug() << "Puck Calibration: Starting..."; // convert the hmd head from sensor space to avatar space glm::mat4 hmdSensorFlippedMat = inputCalibration.hmdSensorMat * Matrices::Y_180; glm::mat4 sensorToAvatarMat = glm::inverse(inputCalibration.avatarMat) * inputCalibration.sensorToWorldMat; @@ -299,18 +300,24 @@ void ViveControllerManager::InputDevice::calibrate(const controller::InputCalibr glm::mat4 defaultToReferenceMat = currentHead * glm::inverse(inputCalibration.defaultHeadMat); int puckCount = (int)_validTrackedObjects.size(); + qDebug() << "Puck Calibration: " << puckCount << " pucks found for calibration"; _config = _preferedConfig; if (_config != Config::Auto && puckCount < MIN_PUCK_COUNT) { + qDebug() << "Puck Calibration: Failed: Could not meet the minimal # of pucks"; uncalibrate(); return; } else if (_config == Config::Auto){ if (puckCount == MIN_PUCK_COUNT) { _config = Config::Feet; + qDebug() << "Puck Calibration: Auto Config: " << configToString(_config) << " configuration"; } else if (puckCount == MIN_FEET_AND_HIPS) { _config = Config::FeetAndHips; + qDebug() << "Puck Calibration: Auto Config: " << configToString(_config) << " configuration"; } else if (puckCount >= MIN_FEET_HIPS_CHEST) { _config = Config::FeetHipsAndChest; + qDebug() << "Puck Calibration: Auto Config: " << configToString(_config) << " configuration"; } else { + qDebug() << "Puck Calibration: Auto Config Failed: Could not meet the minimal # of pucks"; uncalibrate(); return; } @@ -318,8 +325,6 @@ void ViveControllerManager::InputDevice::calibrate(const controller::InputCalibr std::sort(_validTrackedObjects.begin(), _validTrackedObjects.end(), sortPucksYPosition); - - auto& firstFoot = _validTrackedObjects[FIRST_FOOT]; auto& secondFoot = _validTrackedObjects[SECOND_FOOT]; controller::Pose& firstFootPose = firstFoot.second; @@ -349,10 +354,12 @@ void ViveControllerManager::InputDevice::calibrate(const controller::InputCalibr _jointToPuckMap[controller::SPINE2] = _validTrackedObjects[CHEST].first; _pucksOffset[_validTrackedObjects[CHEST].first] = computeOffset(defaultToReferenceMat, inputCalibration.defaultSpine2, _validTrackedObjects[CHEST].second); } else { + qDebug() << "Puck Calibration: " << configToString(_config) << " Config Failed: Could not meet the minimal # of pucks"; uncalibrate(); return; } _calibrated = true; + qDebug() << "PuckCalibration: " << configToString(_config) << " Configuration Successful"; } void ViveControllerManager::InputDevice::uncalibrate() { @@ -618,9 +625,9 @@ void ViveControllerManager::InputDevice::saveSettings() const { settings.endGroup(); } -QString ViveControllerManager::InputDevice::configToString() { +QString ViveControllerManager::InputDevice::configToString(Config config) { QString currentConfig; - switch (_preferedConfig) { + switch (config) { case Config::Auto: currentConfig = "Auto"; break; @@ -658,7 +665,7 @@ void ViveControllerManager::InputDevice::createPreferences() { static const QString VIVE_PUCKS_CONFIG = "Vive Pucks Configuration"; { - auto getter = [this]()->QString { return configToString(); }; + auto getter = [this]()->QString { return configToString(_preferedConfig); }; auto setter = [this](const QString& value) { setConfigFromString(value); saveSettings(); }; auto preference = new ComboBoxPreference(VIVE_PUCKS_CONFIG, "Configuration", getter, setter); QStringList list = (QStringList() << "Auto" << "Feet" << "FeetAndHips" << "FeetHipsAndChest"); diff --git a/plugins/openvr/src/ViveControllerManager.h b/plugins/openvr/src/ViveControllerManager.h index c815506770..d7ab77ddbc 100644 --- a/plugins/openvr/src/ViveControllerManager.h +++ b/plugins/openvr/src/ViveControllerManager.h @@ -129,7 +129,7 @@ private: bool _timeTilCalibrationSet { false }; mutable std::recursive_mutex _lock; - QString configToString(); + QString configToString(Config config); void setConfigFromString(const QString& value); void loadSettings(); void saveSettings() const; diff --git a/scripts/system/makeUserConnection.js b/scripts/system/makeUserConnection.js index a8afad2e1c..37a334bd70 100644 --- a/scripts/system/makeUserConnection.js +++ b/scripts/system/makeUserConnection.js @@ -198,7 +198,7 @@ } var animationData = {}; - function updateAnimationData() { + function updateAnimationData(verticalOffset) { // all we are doing here is moving the right hand to a spot // that is in front of and a bit above the hips. Basing how // far in front as scaling with the avatar's height (say hips @@ -209,6 +209,9 @@ offset = 0.8 * MyAvatar.getAbsoluteJointTranslationInObjectFrame(headIndex).y; } animationData.rightHandPosition = Vec3.multiply(offset, {x: -0.25, y: 0.8, z: 1.3}); + if (verticalOffset) { + animationData.rightHandPosition.y += verticalOffset; + } animationData.rightHandRotation = Quat.fromPitchYawRollDegrees(90, 0, 90); } function shakeHandsAnimation() { @@ -347,7 +350,32 @@ } return false; } - + function findNearestAvatar() { + // We only look some max distance away (much larger than the handshake distance, but still...) + var minDistance = MAX_AVATAR_DISTANCE * 20; + var closestAvatar; + AvatarList.getAvatarIdentifiers().forEach(function (id) { + var avatar = AvatarList.getAvatar(id); + if (avatar && avatar.sessionUUID != MyAvatar.sessionUUID) { + var currentDistance = Vec3.distance(avatar.position, MyAvatar.position); + if (minDistance > currentDistance) { + minDistance = currentDistance; + closestAvatar = avatar; + } + } + }); + return closestAvatar; + } + function adjustAnimationHeight() { + var avatar = findNearestAvatar(); + if (avatar) { + var myHeadIndex = MyAvatar.getJointIndex("Head"); + var otherHeadIndex = avatar.getJointIndex("Head"); + var diff = (avatar.getJointPosition(otherHeadIndex).y - MyAvatar.getJointPosition(myHeadIndex).y) / 2; + print("head height difference: " + diff); + updateAnimationData(diff); + } + } function findNearestWaitingAvatar() { var handPosition = getHandPosition(MyAvatar, currentHandJointIndex); var minDistance = MAX_AVATAR_DISTANCE; @@ -436,6 +464,10 @@ handStringMessageSend({ key: "waiting", }); + // potentially adjust height of handshake + if (fromKeyboard) { + adjustAnimationHeight(); + } lookForWaitingAvatar(); } } diff --git a/unpublishedScripts/marketplace/tablet-raiseHand/tablet-raiseHand.js b/unpublishedScripts/marketplace/tablet-raiseHand/tablet-raiseHand.js new file mode 100644 index 0000000000..f7702053a4 --- /dev/null +++ b/unpublishedScripts/marketplace/tablet-raiseHand/tablet-raiseHand.js @@ -0,0 +1,102 @@ +"use strict"; +// +// tablet-raiseHand.js +// +// client script that creates a tablet button to raise hand +// +// Created by Triplelexx on 17/04/22 +// Copyright 2017 High Fidelity, Inc. +// +// Hand icons adapted from https://linearicons.com, created by Perxis https://perxis.com CC BY-SA 4.0 license. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +(function() { // BEGIN LOCAL_SCOPE + var BUTTON_NAME = "RAISE\nHAND"; + var USERCONNECTION_MESSAGE_CHANNEL = "io.highfidelity.makeUserConnection"; + var DEBUG_PREFIX = "TABLET RAISE HAND: "; + var isRaiseHandButtonActive = false; + var animHandlerId; + + var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + var button = tablet.addButton({ + text: BUTTON_NAME, + icon: "icons/tablet-icons/raise-hand-i.svg", + activeIcon: "icons/tablet-icons/raise-hand-a.svg" + }); + + function onClicked() { + isRaiseHandButtonActive = !isRaiseHandButtonActive; + button.editProperties({ isActive: isRaiseHandButtonActive }); + if (isRaiseHandButtonActive) { + removeAnimation(); + animHandlerId = MyAvatar.addAnimationStateHandler(raiseHandAnimation, []); + Messages.subscribe(USERCONNECTION_MESSAGE_CHANNEL); + Messages.messageReceived.connect(messageHandler); + } else { + removeAnimation(); + Messages.unsubscribe(USERCONNECTION_MESSAGE_CHANNEL); + Messages.messageReceived.disconnect(messageHandler); + } + } + + function removeAnimation() { + if (animHandlerId) { + animHandlerId = MyAvatar.removeAnimationStateHandler(animHandlerId); + } + } + + function raiseHandAnimation(animationProperties) { + // all we are doing here is moving the right hand to a spot that is above the hips. + var headIndex = MyAvatar.getJointIndex("Head"); + var offset = 0.0; + var result = {}; + if (headIndex) { + offset = 0.85 * MyAvatar.getAbsoluteJointTranslationInObjectFrame(headIndex).y; + } + var handPos = Vec3.multiply(offset, { x: -0.7, y: 1.25, z: 0.25 }); + result.rightHandPosition = handPos; + result.rightHandRotation = Quat.fromPitchYawRollDegrees(0, 0, 0); + return result; + } + + function messageHandler(channel, messageString, senderID) { + if (channel !== USERCONNECTION_MESSAGE_CHANNEL && senderID !== MyAvatar.sessionUUID) { + return; + } + var message = {}; + try { + message = JSON.parse(messageString); + } catch (e) { + print(DEBUG_PREFIX + "messageHandler error: " + e); + } + switch (message.key) { + case "waiting": + case "connecting": + case "connectionAck": + case "connectionRequest": + case "done": + removeAnimation(); + if (isRaiseHandButtonActive) { + isRaiseHandButtonActive = false; + button.editProperties({ isActive: isRaiseHandButtonActive }); + } + break; + default: + print(DEBUG_PREFIX + "messageHandler unknown message: " + message); + break; + } + } + + button.clicked.connect(onClicked); + + Script.scriptEnding.connect(function() { + Messages.unsubscribe(USERCONNECTION_MESSAGE_CHANNEL); + Messages.messageReceived.disconnect(messageHandler); + button.clicked.disconnect(onClicked); + tablet.removeButton(button); + removeAnimation(); + }); +}()); // END LOCAL_SCOPE