From 5d606981de0cffacf3e7929b91f90006e9da22e1 Mon Sep 17 00:00:00 2001 From: Anthony Thibault Date: Tue, 8 Jan 2019 18:35:28 -0800 Subject: [PATCH] Added HMD Avatar Alignment Type Adds a new API to set this type, and an option to the Avatar App. * "eye" - Match the user eyes with the avatar eyes. The previous default behavior. If the avatar skull is larger then the user, it can cause the avatars body to lift off of the ground when the user looks down. * "head" - Match the user head with the avatar head. The new default behavior. This prevents the body from lifting off of the ground while wearing a large headed avatar. But can cause an offset between the users eyes and the avatars eyes. --- interface/resources/qml/hifi/AvatarApp.qml | 1 + .../resources/qml/hifi/avatarapp/Settings.qml | 54 ++++++++++++++++++- interface/src/Application.cpp | 10 +++- interface/src/avatar/MyAvatar.cpp | 25 +++++++-- interface/src/avatar/MyAvatar.h | 25 ++++++++- libraries/controllers/src/controllers/Input.h | 6 +++ libraries/shared/src/AvatarConstants.h | 2 +- .../oculus/src/OculusControllerManager.cpp | 11 +++- plugins/openvr/src/ViveControllerManager.cpp | 12 ++++- scripts/system/avatarapp.js | 11 ++++ 10 files changed, 146 insertions(+), 11 deletions(-) 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);