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.
This commit is contained in:
Anthony Thibault 2019-01-08 18:35:28 -08:00
parent 39f4af505c
commit 5d606981de
10 changed files with 146 additions and 11 deletions

View file

@ -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,

View file

@ -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 {

View file

@ -5922,6 +5922,13 @@ void Application::update(float deltaTime) {
auto userInputMapper = DependencyManager::get<UserInputMapper>();
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;

View file

@ -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());

View file

@ -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<QUrl> _prefOverrideAnimGraphUrl;
QUrl _fstAnimGraphOverrideUrl;
bool _useSnapTurn { true };
QString _dominantHand { DOMINANT_RIGHT_HAND };
ThreadSafeValueCache<QString> _dominantHand { DOMINANT_RIGHT_HAND };
ThreadSafeValueCache<QString> _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<QString> _dominantHandSetting;
Setting::Handle<QString> _hmdAvatarAlignmentTypeSetting;
Setting::Handle<float> _headPitchSetting;
Setting::Handle<float> _scaleSetting;
Setting::Handle<float> _yawSpeedSetting;

View file

@ -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 {

View file

@ -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 };

View file

@ -17,6 +17,7 @@
#include <controllers/UserInputMapper.h>
#include <controllers/StandardControls.h>
#include <AvatarConstants.h>
#include <PerfStat.h>
#include <PathUtils.h>
#include <NumericalConstants.h>
@ -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);

View file

@ -27,6 +27,7 @@
#include <SettingHandle.h>
#include <OffscreenUi.h>
#include <GLMHelpers.h>
#include <AvatarConstants.h>
#include <glm/ext.hpp>
#include <glm/gtc/quaternion.hpp>
#include <ui-plugins/PluginContainer.h>
@ -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);
}

View file

@ -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);