diff --git a/interface/resources/qml/hifi/AvatarApp.qml b/interface/resources/qml/hifi/AvatarApp.qml index 171ea4fd15..753b9c5a81 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 b6d0167ba5..39c48646d3 100644 --- a/interface/resources/qml/hifi/avatarapp/Settings.qml +++ b/interface/resources/qml/hifi/avatarapp/Settings.qml @@ -37,6 +37,7 @@ Rectangle { property alias dominantHandIsLeft: leftHandRadioButton.checked property alias otherAvatarsCollisionsOn: otherAvatarsCollisionsEnabledRadiobutton.checked property alias environmentCollisionsOn: environmentCollisionsEnabledRadiobutton.checked + property alias hmdAvatarAlignmentTypeIsEyes: eyesRadioButton.checked property alias avatarAnimationOverrideJSON: avatarAnimationUrlInputText.text property alias avatarAnimationJSON: avatarAnimationUrlInputText.placeholderText property alias avatarCollisionSoundUrl: avatarCollisionSoundUrlInputText.text @@ -65,6 +66,11 @@ Rectangle { } else { environmentCollisionsDisabledRadiobutton.checked = true; } + if (settings.hmdAvatarAlignmentType === 'eyes') { + eyesRadioButton.checked = true; + } else { + headRadioButton.checked = true; + } avatarAnimationJSON = settings.animGraphUrl; avatarAnimationOverrideJSON = settings.animGraphOverrideUrl; @@ -210,7 +216,7 @@ Rectangle { anchors.left: parent.left anchors.right: parent.right - rows: 2 + rows: 4 rowSpacing: 25 columns: 3 @@ -233,7 +239,7 @@ Rectangle { Layout.row: 0 Layout.column: 1 - Layout.leftMargin: -20 + Layout.leftMargin: -15 ButtonGroup.group: leftRight checked: true @@ -249,7 +255,7 @@ Rectangle { id: rightHandRadioButton Layout.row: 0 - Layout.column: 3 + Layout.column: 2 Layout.rightMargin: -15 ButtonGroup.group: leftRight @@ -260,7 +266,7 @@ Rectangle { text: "Right" boxSize: 20 } - + HifiConstants { id: hifi } @@ -272,17 +278,17 @@ Rectangle { Layout.column: 0 text: "Avatar to avatar collision" } - + ButtonGroup { id: otherAvatarsOnOff } - + HifiControlsUit.RadioButton { id: otherAvatarsCollisionsEnabledRadiobutton Layout.row: 1 Layout.column: 1 - Layout.leftMargin: -20 + Layout.leftMargin: -15 ButtonGroup.group: otherAvatarsOnOff @@ -297,7 +303,7 @@ Rectangle { id: otherAvatarsCollisionsDisabledRadiobutton Layout.row: 1 - Layout.column: 3 + Layout.column: 2 Layout.rightMargin: -15 ButtonGroup.group: otherAvatarsOnOff @@ -320,13 +326,13 @@ Rectangle { ButtonGroup { id: worldOnOff } - + HifiControlsUit.RadioButton { id: environmentCollisionsEnabledRadiobutton Layout.row: 2 Layout.column: 1 - Layout.leftMargin: -20 + Layout.leftMargin: -15 ButtonGroup.group: worldOnOff @@ -341,7 +347,7 @@ Rectangle { id: environmentCollisionsDisabledRadiobutton Layout.row: 2 - Layout.column: 3 + Layout.column: 2 Layout.rightMargin: -15 ButtonGroup.group: worldOnOff @@ -352,6 +358,52 @@ Rectangle { text: "Off" boxSize: 20 } + + // TextStyle9 + RalewaySemiBold { + size: 17; + Layout.row: 3 + Layout.column: 0 + text: "HMD Alignment" + } + + ButtonGroup { + id: headEyes + } + + HifiControlsUit.RadioButton { + id: headRadioButton + + Layout.row: 3 + Layout.column: 1 + Layout.leftMargin: -15 + + ButtonGroup.group: headEyes + checked: true + + colorScheme: hifi.colorSchemes.light + fontSize: 17 + letterSpacing: 1.4 + text: "Head" + boxSize: 20 + } + + HifiControlsUit.RadioButton { + id: eyesRadioButton + + Layout.row: 3 + Layout.column: 2 + Layout.rightMargin: -15 + + 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 30d422949f..47250a1215 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -6062,6 +6062,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()), @@ -6075,7 +6082,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 fec988b29f..5b821374a5 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -125,6 +125,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), @@ -286,10 +287,25 @@ MyAvatar::~MyAvatar() { _myScriptEngine = nullptr; } +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); } } @@ -377,6 +393,7 @@ void MyAvatar::resetSensorsAndBody() { if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "resetSensorsAndBody"); return; + } qApp->getActiveDisplayPlugin()->resetSensors(); @@ -1277,7 +1294,8 @@ void MyAvatar::resizeAvatarEntitySettingHandles(uint32_t maxIndex) { } void MyAvatar::saveData() { - _dominantHandSetting.set(_dominantHand); + _dominantHandSetting.set(getDominantHand()); + _hmdAvatarAlignmentTypeSetting.set(getHmdAvatarAlignmentType()); _headPitchSetting.set(getHead()->getBasePitch()); _scaleSetting.set(_targetScale); _yawSpeedSetting.set(_yawSpeed); @@ -1882,6 +1900,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 af08955ca0..b365ed4088 100755 --- 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"; using Clock = std::chrono::system_clock; using TimePoint = Clock::time_point; @@ -519,7 +520,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 @@ -1585,6 +1597,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 @@ -1773,7 +1792,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 @@ -1946,6 +1966,7 @@ private: TimePoint _nextTraitsSendWindow; 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 76ff4a1755..c2b9145f3c 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 3aea5f1ce0..34ebb73fda 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 631c0e03e8..fb61b914a3 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);