diff --git a/interface/resources/qml/hifi/AvatarApp.qml b/interface/resources/qml/hifi/AvatarApp.qml index bfa37385a5..f12cfd88b0 100644 --- a/interface/resources/qml/hifi/AvatarApp.qml +++ b/interface/resources/qml/hifi/AvatarApp.qml @@ -254,6 +254,7 @@ Rectangle { onSaveClicked: function() { var avatarSettings = { dominantHand : settings.dominantHandIsLeft ? 'left' : 'right', + hmdAvatarAlignmentType : settings.hmdAvatarAlignmentTypeIsEyes ? 'eyes' : 'head', collisionsEnabled : settings.environmentCollisionsOn, otherAvatarsCollisionsEnabled : settings.otherAvatarsCollisionsOn, animGraphOverrideUrl : settings.avatarAnimationOverrideJSON, diff --git a/interface/resources/qml/hifi/avatarapp/Settings.qml b/interface/resources/qml/hifi/avatarapp/Settings.qml index d212186c5e..7ef6047665 100644 --- a/interface/resources/qml/hifi/avatarapp/Settings.qml +++ b/interface/resources/qml/hifi/avatarapp/Settings.qml @@ -36,6 +36,7 @@ Rectangle { property real scaleValue: scaleSlider.value / 10 property alias dominantHandIsLeft: leftHandRadioButton.checked property alias otherAvatarsCollisionsOn: otherAvatarsCollisionsEnabledCheckBox.checked + property alias hmdAvatarAlignmentTypeIsEyes: eyesRadioButton.checked property alias environmentCollisionsOn: environmentCollisionsEnabledCheckBox.checked property alias avatarAnimationOverrideJSON: avatarAnimationUrlInputText.text property alias avatarAnimationJSON: avatarAnimationUrlInputText.placeholderText @@ -61,6 +62,11 @@ Rectangle { if (settings.collisionsEnabled) { environmentCollisionsEnabledCheckBox.checked = true; } + if (settings.hmdAvatarAlignmentType === 'eyes') { + eyesRadioButton.checked = true; + } else { + headRadioButton.checked = true; + } avatarAnimationJSON = settings.animGraphUrl; avatarAnimationOverrideJSON = settings.animGraphOverrideUrl; @@ -256,7 +262,7 @@ Rectangle { text: "Right" boxSize: 20 } - + HifiConstants { id: hifi } @@ -294,6 +300,52 @@ Rectangle { Layout.leftMargin: 60 colorScheme: hifi.colorSchemes.light } + + // TextStyle9 + RalewaySemiBold { + size: 17; + Layout.row: 4 + Layout.column: 0 + text: "HMD Alignment" + } + + ButtonGroup { + id: headEyes + } + + HifiControlsUit.RadioButton { + id: headRadioButton + + Layout.row: 4 + Layout.column: 1 + Layout.leftMargin: -40 + + ButtonGroup.group: headEyes + checked: true + + colorScheme: hifi.colorSchemes.light + fontSize: 17 + letterSpacing: 1.4 + text: "Head" + boxSize: 20 + } + + HifiControlsUit.RadioButton { + id: eyesRadioButton + + Layout.row: 4 + Layout.column: 2 + Layout.rightMargin: 20 + + ButtonGroup.group: headEyes + + colorScheme: hifi.colorSchemes.light + fontSize: 17 + letterSpacing: 1.4 + text: "Eyes" + boxSize: 20 + } + } ColumnLayout { diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index d306c77cce..3768640cf7 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -5922,6 +5922,13 @@ void Application::update(float deltaTime) { auto userInputMapper = DependencyManager::get(); + controller::HmdAvatarAlignmentType hmdAvatarAlignmentType; + if (myAvatar->getHmdAvatarAlignmentType() == "eyes") { + hmdAvatarAlignmentType = controller::HmdAvatarAlignmentType::Eyes; + } else { + hmdAvatarAlignmentType = controller::HmdAvatarAlignmentType::Head; + } + controller::InputCalibrationData calibrationData = { myAvatar->getSensorToWorldMatrix(), createMatFromQuatAndPos(myAvatar->getWorldOrientation(), myAvatar->getWorldPosition()), @@ -5935,7 +5942,8 @@ void Application::update(float deltaTime) { myAvatar->getRightArmCalibrationMat(), myAvatar->getLeftArmCalibrationMat(), myAvatar->getRightHandCalibrationMat(), - myAvatar->getLeftHandCalibrationMat() + myAvatar->getLeftHandCalibrationMat(), + hmdAvatarAlignmentType }; InputPluginPointer keyboardMousePlugin; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index e40fc7f9dd..23efe0689b 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -124,6 +124,7 @@ MyAvatar::MyAvatar(QThread* thread) : _prevShouldDrawHead(true), _audioListenerMode(FROM_HEAD), _dominantHandSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "dominantHand", DOMINANT_RIGHT_HAND), + _hmdAvatarAlignmentTypeSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "hmdAvatarAlignmentType", DEFAULT_HMD_AVATAR_ALIGNMENT_TYPE), _headPitchSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "", 0.0f), _scaleSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "scale", _targetScale), _yawSpeedSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "yawSpeed", _yawSpeed), @@ -283,10 +284,25 @@ MyAvatar::~MyAvatar() { _lookAtTargetAvatar.reset(); } +QString MyAvatar::getDominantHand() const { + return _dominantHand.get(); +} + void MyAvatar::setDominantHand(const QString& hand) { if (hand == DOMINANT_LEFT_HAND || hand == DOMINANT_RIGHT_HAND) { - _dominantHand = hand; - emit dominantHandChanged(_dominantHand); + _dominantHand.set(hand); + emit dominantHandChanged(hand); + } +} + +QString MyAvatar::getHmdAvatarAlignmentType() const { + return _hmdAvatarAlignmentType.get(); +} + +void MyAvatar::setHmdAvatarAlignmentType(const QString& type) { + if (type != _hmdAvatarAlignmentType.get()) { + _hmdAvatarAlignmentType.set(type); + emit hmdAvatarAlignmentTypeChanged(type); } } @@ -374,6 +390,7 @@ void MyAvatar::resetSensorsAndBody() { if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "resetSensorsAndBody"); return; + } qApp->getActiveDisplayPlugin()->resetSensors(); @@ -1273,7 +1290,8 @@ void MyAvatar::resizeAvatarEntitySettingHandles(unsigned int avatarEntityIndex) } void MyAvatar::saveData() { - _dominantHandSetting.set(_dominantHand); + _dominantHandSetting.set(getDominantHand()); + _hmdAvatarAlignmentTypeSetting.set(getHmdAvatarAlignmentType()); _headPitchSetting.set(getHead()->getBasePitch()); _scaleSetting.set(_targetScale); _yawSpeedSetting.set(_yawSpeed); @@ -1444,6 +1462,7 @@ void MyAvatar::loadData() { setCollisionSoundURL(_collisionSoundURLSetting.get(QUrl(DEFAULT_AVATAR_COLLISION_SOUND_URL)).toString()); setSnapTurn(_useSnapTurnSetting.get()); setDominantHand(_dominantHandSetting.get(DOMINANT_RIGHT_HAND).toLower()); + setHmdAvatarAlignmentType(_hmdAvatarAlignmentTypeSetting.get(DEFAULT_HMD_AVATAR_ALIGNMENT_TYPE).toLower()); setUserHeight(_userHeightSetting.get(DEFAULT_AVATAR_HEIGHT)); setTargetScale(_scaleSetting.get()); diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 17b71153ea..139470fb86 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -252,6 +252,7 @@ class MyAvatar : public Avatar { const QString DOMINANT_LEFT_HAND = "left"; const QString DOMINANT_RIGHT_HAND = "right"; + const QString DEFAULT_HMD_AVATAR_ALIGNMENT_TYPE = "head"; public: enum DriveKeys { @@ -512,7 +513,18 @@ public: * @function MyAvatar.getDominantHand * @returns {string} */ - Q_INVOKABLE QString getDominantHand() const { return _dominantHand; } + Q_INVOKABLE QString getDominantHand() const; + + /**jsdoc + * @function MyAvatar.setHmdAvatarAlignmentType + * @param {string} hand + */ + Q_INVOKABLE void setHmdAvatarAlignmentType(const QString& hand); + /**jsdoc + * @function MyAvatar.setHmdAvatarAlignmentType + * @returns {string} + */ + Q_INVOKABLE QString getHmdAvatarAlignmentType() const; /**jsdoc * @function MyAvatar.setCenterOfGravityModelEnabled @@ -1565,6 +1577,13 @@ signals: */ void dominantHandChanged(const QString& hand); + /**jsdoc + * @function MyAvatar.hmdAvatarAlignmentTypeChanged + * @param {string} type + * @returns {Signal} + */ + void hmdAvatarAlignmentTypeChanged(const QString& type); + /**jsdoc * @function MyAvatar.sensorToWorldScaleChanged * @param {number} scale @@ -1752,7 +1771,8 @@ private: ThreadSafeValueCache _prefOverrideAnimGraphUrl; QUrl _fstAnimGraphOverrideUrl; bool _useSnapTurn { true }; - QString _dominantHand { DOMINANT_RIGHT_HAND }; + ThreadSafeValueCache _dominantHand { DOMINANT_RIGHT_HAND }; + ThreadSafeValueCache _hmdAvatarAlignmentType { DEFAULT_HMD_AVATAR_ALIGNMENT_TYPE }; const float ROLL_CONTROL_DEAD_ZONE_DEFAULT = 8.0f; // degrees const float ROLL_CONTROL_RATE_DEFAULT = 114.0f; // degrees / sec @@ -1922,6 +1942,7 @@ private: bool _skeletonModelLoaded { false }; Setting::Handle _dominantHandSetting; + Setting::Handle _hmdAvatarAlignmentTypeSetting; Setting::Handle _headPitchSetting; Setting::Handle _scaleSetting; Setting::Handle _yawSpeedSetting; diff --git a/libraries/controllers/src/controllers/Input.h b/libraries/controllers/src/controllers/Input.h index 3c01ee0942..f2a5ca1296 100644 --- a/libraries/controllers/src/controllers/Input.h +++ b/libraries/controllers/src/controllers/Input.h @@ -15,6 +15,11 @@ namespace controller { +enum class HmdAvatarAlignmentType { + Eyes = 0, // align the user's eyes with the avatars eyes + Head // align the user's head with the avatars head +}; + struct InputCalibrationData { glm::mat4 sensorToWorldMat; // sensor to world glm::mat4 avatarMat; // avatar to world @@ -29,6 +34,7 @@ struct InputCalibrationData { glm::mat4 defaultLeftArm; // default pose for leftArm joint in sensor space glm::mat4 defaultRightHand; // default pose for rightHand joint in sensor space glm::mat4 defaultLeftHand; // default pose for leftHand joint in sensor space + HmdAvatarAlignmentType hmdAvatarAlignmentType; }; enum class ChannelType { diff --git a/libraries/shared/src/AvatarConstants.h b/libraries/shared/src/AvatarConstants.h index 87da47a27a..103782bd3f 100644 --- a/libraries/shared/src/AvatarConstants.h +++ b/libraries/shared/src/AvatarConstants.h @@ -44,7 +44,7 @@ const float DEFAULT_AVATAR_RIGHTHAND_MASS = 2.0f; // Used when avatar is missing joints... (avatar space) const glm::quat DEFAULT_AVATAR_MIDDLE_EYE_ROT { Quaternions::Y_180 }; -const glm::vec3 DEFAULT_AVATAR_HEAD_TO_MIDDLE_EYE_OFFSET = { 0.0f, 0.06f, -0.09f }; +const glm::vec3 DEFAULT_AVATAR_HEAD_TO_MIDDLE_EYE_OFFSET = { 0.0f, 0.064f, 0.084f }; const glm::vec3 DEFAULT_AVATAR_HEAD_POS { 0.0f, 0.53f, 0.0f }; const glm::quat DEFAULT_AVATAR_HEAD_ROT { Quaternions::Y_180 }; const glm::vec3 DEFAULT_AVATAR_RIGHTARM_POS { -0.134824f, 0.396348f, -0.0515777f }; diff --git a/plugins/oculus/src/OculusControllerManager.cpp b/plugins/oculus/src/OculusControllerManager.cpp index 392d990638..f42f32818d 100644 --- a/plugins/oculus/src/OculusControllerManager.cpp +++ b/plugins/oculus/src/OculusControllerManager.cpp @@ -17,6 +17,7 @@ #include #include +#include #include #include #include @@ -327,8 +328,14 @@ void OculusControllerManager::TouchDevice::handleHeadPose(float deltaTime, ovr::toGlm(headPose.AngularVelocity)); glm::mat4 sensorToAvatar = glm::inverse(inputCalibrationData.avatarMat) * inputCalibrationData.sensorToWorldMat; - glm::mat4 defaultHeadOffset = glm::inverse(inputCalibrationData.defaultCenterEyeMat) * - inputCalibrationData.defaultHeadMat; + glm::mat4 defaultHeadOffset; + if (inputCalibrationData.hmdAvatarAlignmentType == controller::HmdAvatarAlignmentType::Eyes) { + // align the eyes of the user with the eyes of the avatar + defaultHeadOffset = glm::inverse(inputCalibrationData.defaultCenterEyeMat) * inputCalibrationData.defaultHeadMat; + } else { + // align the head of the user with the head of the avatar + defaultHeadOffset = createMatFromQuatAndPos(Quaternions::IDENTITY, -DEFAULT_AVATAR_HEAD_TO_MIDDLE_EYE_OFFSET); + } pose.valid = true; _poseStateMap[controller::HEAD] = pose.postTransform(defaultHeadOffset).transform(sensorToAvatar); diff --git a/plugins/openvr/src/ViveControllerManager.cpp b/plugins/openvr/src/ViveControllerManager.cpp index 53c23403a5..cbd96d8ee9 100644 --- a/plugins/openvr/src/ViveControllerManager.cpp +++ b/plugins/openvr/src/ViveControllerManager.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -1024,7 +1025,16 @@ void ViveControllerManager::InputDevice::handleHeadPoseEvent(const controller::I //perform a 180 flip to make the HMD face the +z instead of -z, beacuse the head faces +z glm::mat4 matYFlip = mat * Matrices::Y_180; controller::Pose pose(extractTranslation(matYFlip), glmExtractRotation(matYFlip), linearVelocity, angularVelocity); - glm::mat4 defaultHeadOffset = glm::inverse(inputCalibrationData.defaultCenterEyeMat) * inputCalibrationData.defaultHeadMat; + + glm::mat4 defaultHeadOffset; + if (inputCalibrationData.hmdAvatarAlignmentType == controller::HmdAvatarAlignmentType::Eyes) { + // align the eyes of the user with the eyes of the avatar + defaultHeadOffset = glm::inverse(inputCalibrationData.defaultCenterEyeMat) * inputCalibrationData.defaultHeadMat; + } else { + // align the head of the user with the head of the avatar + defaultHeadOffset = createMatFromQuatAndPos(Quaternions::IDENTITY, -DEFAULT_AVATAR_HEAD_TO_MIDDLE_EYE_OFFSET); + } + glm::mat4 sensorToAvatar = glm::inverse(inputCalibrationData.avatarMat) * inputCalibrationData.sensorToWorldMat; _poseStateMap[controller::HEAD] = pose.postTransform(defaultHeadOffset).transform(sensorToAvatar); } diff --git a/scripts/system/avatarapp.js b/scripts/system/avatarapp.js index 2b9a738202..d63de91a7d 100644 --- a/scripts/system/avatarapp.js +++ b/scripts/system/avatarapp.js @@ -62,6 +62,7 @@ function getMyAvatar() { function getMyAvatarSettings() { return { dominantHand: MyAvatar.getDominantHand(), + hmdAvatarAlignmentType: MyAvatar.getHmdAvatarAlignmentType(), collisionsEnabled: MyAvatar.getCollisionsEnabled(), otherAvatarsCollisionsEnabled: MyAvatar.getOtherAvatarsCollisionsEnabled(), collisionSoundUrl : MyAvatar.collisionSoundURL, @@ -129,6 +130,13 @@ function onDominantHandChanged(dominantHand) { } } +function onHmdAvatarAlignmentTypeChanged(type) { + if (currentAvatarSettings.hmdAvatarAlignmentType !== type) { + currentAvatarSettings.hmdAvatarAlignmentType = type; + sendToQml({'method' : 'settingChanged', 'name' : 'hmdAvatarAlignmentType', 'value' : type}); + } +} + function onCollisionsEnabledChanged(enabled) { if(currentAvatarSettings.collisionsEnabled !== enabled) { currentAvatarSettings.collisionsEnabled = enabled; @@ -331,6 +339,7 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See currentAvatar.avatarScale = message.avatarScale; MyAvatar.setDominantHand(message.settings.dominantHand); + MyAvatar.setHmdAvatarAlignmentType(message.settings.hmdAvatarAlignmentType); MyAvatar.setOtherAvatarsCollisionsEnabled(message.settings.otherAvatarsCollisionsEnabled); MyAvatar.setCollisionsEnabled(message.settings.collisionsEnabled); MyAvatar.collisionSoundURL = message.settings.collisionSoundUrl; @@ -521,6 +530,7 @@ function off() { Entities.deletingWearable.disconnect(onDeletingWearable); MyAvatar.skeletonModelURLChanged.disconnect(onSkeletonModelURLChanged); MyAvatar.dominantHandChanged.disconnect(onDominantHandChanged); + MyAvatar.hmdAvatarAlignmentTypeChanged.disconnect(onHmdAvatarAlignmentTypeChanged); MyAvatar.collisionsEnabledChanged.disconnect(onCollisionsEnabledChanged); MyAvatar.otherAvatarsCollisionsEnabledChanged.disconnect(onOtherAvatarsCollisionsEnabledChanged); MyAvatar.newCollisionSoundURL.disconnect(onNewCollisionSoundUrl); @@ -542,6 +552,7 @@ function on() { Entities.deletingWearable.connect(onDeletingWearable); MyAvatar.skeletonModelURLChanged.connect(onSkeletonModelURLChanged); MyAvatar.dominantHandChanged.connect(onDominantHandChanged); + MyAvatar.hmdAvatarAlignmentTypeChanged.connect(onHmdAvatarAlignmentTypeChanged); MyAvatar.collisionsEnabledChanged.connect(onCollisionsEnabledChanged); MyAvatar.otherAvatarsCollisionsEnabledChanged.connect(onOtherAvatarsCollisionsEnabledChanged); MyAvatar.newCollisionSoundURL.connect(onNewCollisionSoundUrl);