From 2179c153de8bd8d1b455894e9ccd3699df133c3f Mon Sep 17 00:00:00 2001 From: Phil Palmer Date: Tue, 22 Dec 2020 14:22:27 -0500 Subject: [PATCH 01/28] VR fixes for: couldn't sit on the floor, wrong walk directions. - Divided the option "Avatar leaning behavior" into two options that work more usefully: "Allow my avatar to stand" and "Allow my avatar to lean" (PreferencesDialog.cpp). Made the necessary fixes so that the avatar can be set to stand only when the user is standing (more details below). - The logic controlling the direction of MyAvatar's action motor is now centralised in calculateScaledDirection (was previously split between there and updateMotors). calculateScaledDirection now returns a velocity in world space. - CharacterController::FollowHelper now uses separate follow timers for rotation, horizontal and vertical (previously followed all three based on the longest of their follow times). Where appropriate, FollowHelper can now snap immediately to the desired rotation/horizontal/vertical independently (see FOLLOW_TIME_IMMEDIATE_SNAP). - FollowHelper::FollowType has therefore moved to CharacterController::FollowType. - MyAvatar::FollowHelper::postPhysicsUpdate: If MyAvatar is not allowed to stand when the user is sitting, this now avoids recentring the body based on the head height. - Removed Q_PROPERTY(MyAvatar::SitStandModelType, as the sitting/standing/leaning model uses different enums now (see setAllowAvatarStandingPreference, setAllowAvatarLeaningPreference). - Removed Q_PROPERTY(bool isSitStandStateLocked which is no longer used, because we now always track the user's real-world sit/stand state, regardless of what we're doing with it. - MyAvatar::FollowHelper::shouldActivateHorizontal: If MyAvatar is not allowed to lean, this now returns true to recentre the footing if the head is outside the base of support. - MyAvatar::FollowHelper::shouldActivateHorizontalCG: If MyAvatar is not allowed to lean, this now always returns true to recentre the footing. Rearranged to avoid computing values that weren't used depending on the conditions. Resolved some duplicated code. - MyAvatar::setUserRecenterModel previously set HMDLeanRecenterEnabled based on the chosen mode, but it got reset when getting out of a sit. Now HMDLeanRecenterEnabled is only controlled by the scripts. - Added Rig::getUnscaledHipsHeight (like getUnscaledEyeHeight). Refactored a little to avoid duplicated code. Added DEFAULT_AVATAR_HIPS_HEIGHT which is the value that Rig::getUnscaledHipsHeight returns when using the default avatar. - Fix for recentring not behaving as requested by the user after getting up from click-to-sit (always behaving like 'Auto') : MyAvatar::endSit now passes false to centerBody for 'forceFollowYPos'. - Fix for incorrect vertical position of the avatar and viewpoint after changing lean recentre mode while not standing in the real world: MyAvatar::setAllowAvatarStandingPreference now calls centerBody with false for 'forceFollowYPos'. - computeHipsInSensorFrame: The code now matches the comments in that it only skips the dampening of the hips rotation if the centre-of-gravity model is being used. --- interface/src/avatar/MyAvatar.cpp | 738 +++++++++++------- interface/src/avatar/MyAvatar.h | 93 ++- .../src/avatar/MyCharacterController.cpp | 4 +- interface/src/avatar/MyCharacterController.h | 2 +- interface/src/avatar/MySkeletonModel.cpp | 15 +- interface/src/ui/AnimStats.cpp | 6 +- interface/src/ui/PreferencesDialog.cpp | 62 +- libraries/animation/src/Rig.cpp | 51 +- libraries/animation/src/Rig.h | 6 + libraries/physics/src/CharacterController.cpp | 131 ++-- libraries/physics/src/CharacterController.h | 23 +- libraries/shared/src/AvatarConstants.h | 1 + 12 files changed, 691 insertions(+), 441 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index b3231b906d..e5413105f8 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -96,10 +96,8 @@ const float CENTIMETERS_PER_METER = 100.0f; const QString AVATAR_SETTINGS_GROUP_NAME { "Avatar" }; -static const QString USER_RECENTER_MODEL_FORCE_SIT = QStringLiteral("ForceSit"); -static const QString USER_RECENTER_MODEL_FORCE_STAND = QStringLiteral("ForceStand"); -static const QString USER_RECENTER_MODEL_AUTO = QStringLiteral("Auto"); -static const QString USER_RECENTER_MODEL_DISABLE_HMD_LEAN = QStringLiteral("DisableHMDLean"); +static const QString ALLOW_AVATAR_STANDING_ALWAYS = QStringLiteral("Always"); +static const QString ALLOW_AVATAR_STANDING_WHEN_USER_IS_STANDING = QStringLiteral("UserStanding"); const QString HEAD_BLEND_DIRECTIONAL_ALPHA_NAME = "lookAroundAlpha"; const QString HEAD_BLEND_LINEAR_ALPHA_NAME = "lookBlendAlpha"; @@ -111,30 +109,38 @@ const QString POINT_BLEND_LINEAR_ALPHA_NAME = "pointBlendAlpha"; const QString POINT_REF_JOINT_NAME = "RightShoulder"; const float POINT_ALPHA_BLENDING = 1.0f; -MyAvatar::SitStandModelType stringToUserRecenterModel(const QString& str) { - if (str == USER_RECENTER_MODEL_FORCE_SIT) { - return MyAvatar::ForceSit; - } else if (str == USER_RECENTER_MODEL_FORCE_STAND) { - return MyAvatar::ForceStand; - } else if (str == USER_RECENTER_MODEL_DISABLE_HMD_LEAN) { - return MyAvatar::DisableHMDLean; - } else { - return MyAvatar::Auto; +const std::array(MyAvatar::AllowAvatarStandingPreference::Count)> + MyAvatar::allowAvatarStandingPreferenceStrings = { + QStringLiteral("WhenUserIsStanding"), + QStringLiteral("Always") +}; + +const std::array(MyAvatar::AllowAvatarLeaningPreference::Count)> + MyAvatar::allowAvatarLeaningPreferenceStrings = { + QStringLiteral("WhenUserIsStanding"), + QStringLiteral("Always"), + QStringLiteral("Never"), + QStringLiteral("AlwaysNoRecenter") +}; + +MyAvatar::AllowAvatarStandingPreference stringToAllowAvatarStandingPreference(const QString& str) { + for (uint stringIndex = 0; stringIndex < static_cast(MyAvatar::AllowAvatarStandingPreference::Count); stringIndex++) { + if (MyAvatar::allowAvatarStandingPreferenceStrings[stringIndex] == str) { + return static_cast(stringIndex); + } } + + return MyAvatar::AllowAvatarStandingPreference::Default; } -QString userRecenterModelToString(MyAvatar::SitStandModelType model) { - switch (model) { - case MyAvatar::ForceSit: - return USER_RECENTER_MODEL_FORCE_SIT; - case MyAvatar::ForceStand: - return USER_RECENTER_MODEL_FORCE_STAND; - case MyAvatar::DisableHMDLean: - return USER_RECENTER_MODEL_DISABLE_HMD_LEAN; - case MyAvatar::Auto: - default: - return USER_RECENTER_MODEL_AUTO; +MyAvatar::AllowAvatarLeaningPreference stringToAllowAvatarLeaningPreference(const QString& str) { + for (uint stringIndex = 0; stringIndex < static_cast(MyAvatar::AllowAvatarLeaningPreference::Count); stringIndex++) { + if (MyAvatar::allowAvatarLeaningPreferenceStrings[stringIndex] == str) { + return static_cast(stringIndex); + } } + + return MyAvatar::AllowAvatarLeaningPreference::Default; } static const QStringList TRIGGER_REACTION_NAMES = { @@ -166,7 +172,7 @@ MyAvatar::MyAvatar(QThread* thread) : _scriptedMotorFrame(SCRIPTED_MOTOR_CAMERA_FRAME), _scriptedMotorMode(SCRIPTED_MOTOR_SIMPLE_MODE), _motionBehaviors(AVATAR_MOTION_DEFAULTS), - _characterController(std::shared_ptr(this)), + _characterController(std::shared_ptr(this), _follow._timeRemaining), _eyeContactTarget(LEFT_EYE), _realWorldFieldOfView("realWorldFieldOfView", DEFAULT_REAL_WORLD_FIELD_OF_VIEW_DEGREES), @@ -214,8 +220,12 @@ MyAvatar::MyAvatar(QThread* thread) : _analogWalkSpeedSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "analogWalkSpeed", _analogWalkSpeed.get()), _analogPlusWalkSpeedSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "analogPlusWalkSpeed", _analogPlusWalkSpeed.get()), _controlSchemeIndexSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "controlSchemeIndex", _controlSchemeIndex), - _userRecenterModelSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "userRecenterModel", USER_RECENTER_MODEL_AUTO) -{ + _allowAvatarStandingPreferenceSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "allowAvatarStandingPreference", + allowAvatarStandingPreferenceStrings[static_cast( + AllowAvatarStandingPreference::Default)]), + _allowAvatarLeaningPreferenceSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "allowAvatarLeaningPreference", + allowAvatarLeaningPreferenceStrings[static_cast( + AllowAvatarLeaningPreference::Default)]) { _clientTraitsHandler.reset(new ClientTraitsHandler(this)); // give the pointer to our head to inherited _headData variable from AvatarData @@ -487,14 +497,17 @@ void MyAvatar::resetSensorsAndBody() { reset(true, false, true); } -void MyAvatar::centerBody() { +// forceFollowYPos: true to force the body matrix to be affected by the HMD's +// vertical position, even if crouch recentering is disabled. +void MyAvatar::centerBody(const bool forceFollowYPos) { if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "centerBody"); return; } // derive the desired body orientation from the current hmd orientation, before the sensor reset. - auto newBodySensorMatrix = deriveBodyFromHMDSensor(); // Based on current cached HMD position/rotation.. + auto newBodySensorMatrix = + deriveBodyFromHMDSensor(forceFollowYPos); // Based on current cached HMD position/rotation.. // transform this body into world space auto worldBodyMatrix = _sensorToWorldMatrix * newBodySensorMatrix; @@ -571,64 +584,63 @@ void MyAvatar::reset(bool andRecenter, bool andReload, bool andHead) { } } +// Determine if the user is sitting or standing in the real world. void MyAvatar::updateSitStandState(float newHeightReading, float dt) { const float STANDING_HEIGHT_MULTIPLE = 1.2f; const float SITTING_HEIGHT_MULTIPLE = 0.833f; const float SITTING_TIMEOUT = 4.0f; // 4 seconds const float STANDING_TIMEOUT = 0.3333f; // 1/3 second const float SITTING_UPPER_BOUND = 1.52f; - if (!getIsSitStandStateLocked()) { - if (!getIsAway() && getControllerPoseInAvatarFrame(controller::Action::HEAD).isValid()) { - if (getIsInSittingState()) { - if (newHeightReading > (STANDING_HEIGHT_MULTIPLE * _tippingPoint)) { - // if we recenter upwards then no longer in sitting state - _sitStandStateTimer += dt; - if (_sitStandStateTimer > STANDING_TIMEOUT) { - _averageUserHeightSensorSpace = newHeightReading; - _tippingPoint = newHeightReading; - setIsInSittingState(false); - } - } else if (newHeightReading < (SITTING_HEIGHT_MULTIPLE * _tippingPoint)) { - // if we are mis labelled as sitting but we are standing in the real world this will - // make sure that a real sit is still recognized so we won't be stuck in sitting unable to change state - _sitStandStateTimer += dt; - if (_sitStandStateTimer > SITTING_TIMEOUT) { - _averageUserHeightSensorSpace = newHeightReading; - _tippingPoint = newHeightReading; - // here we stay in sit state but reset the average height - setIsInSittingState(true); - } - } else { - // sanity check if average height greater than 5ft they are not sitting(or get off your dangerous barstool please) - if (_averageUserHeightSensorSpace > SITTING_UPPER_BOUND) { - setIsInSittingState(false); - } else { - // tipping point is average height when sitting. - _tippingPoint = _averageUserHeightSensorSpace; - _sitStandStateTimer = 0.0f; - } + if (!getIsAway() && getControllerPoseInAvatarFrame(controller::Action::HEAD).isValid()) { + if (getIsInSittingState()) { + if (newHeightReading > (STANDING_HEIGHT_MULTIPLE * _tippingPoint)) { + // if we recenter upwards then no longer in sitting state + _sitStandStateTimer += dt; + if (_sitStandStateTimer > STANDING_TIMEOUT) { + _averageUserHeightSensorSpace = newHeightReading; + _tippingPoint = newHeightReading; + setIsInSittingState(false); + } + } else if (newHeightReading < (SITTING_HEIGHT_MULTIPLE * _tippingPoint)) { + // if we are mis labelled as sitting but we are standing in the real world this will + // make sure that a real sit is still recognized so we won't be stuck in sitting unable to change state + _sitStandStateTimer += dt; + if (_sitStandStateTimer > SITTING_TIMEOUT) { + _averageUserHeightSensorSpace = newHeightReading; + _tippingPoint = newHeightReading; + // here we stay in sit state but reset the average height + setIsInSittingState(true); } } else { - // in the standing state - if (newHeightReading < (SITTING_HEIGHT_MULTIPLE * _tippingPoint)) { - _sitStandStateTimer += dt; - if (_sitStandStateTimer > SITTING_TIMEOUT) { - _averageUserHeightSensorSpace = newHeightReading; - _tippingPoint = newHeightReading; - setIsInSittingState(true); - } + // sanity check if average height greater than 5ft they are not sitting(or get off your dangerous barstool please) + if (_averageUserHeightSensorSpace > SITTING_UPPER_BOUND) { + setIsInSittingState(false); } else { - // use the mode height for the tipping point when we are standing. - _tippingPoint = getCurrentStandingHeight(); + // tipping point is average height when sitting. + _tippingPoint = _averageUserHeightSensorSpace; _sitStandStateTimer = 0.0f; } } } else { - //if you are away then reset the average and set state to standing. - _averageUserHeightSensorSpace = _userHeight.get(); - _tippingPoint = _userHeight.get(); - setIsInSittingState(false); + // in the standing state + if (newHeightReading < (SITTING_HEIGHT_MULTIPLE * _tippingPoint)) { + _sitStandStateTimer += dt; + if (_sitStandStateTimer > SITTING_TIMEOUT) { + _averageUserHeightSensorSpace = newHeightReading; + _tippingPoint = newHeightReading; + setIsInSittingState(true); + } + } else { + // use the mode height for the tipping point when we are standing. + _tippingPoint = getCurrentStandingHeight(); + _sitStandStateTimer = 0.0f; + } } + } else { + //if you are away then reset the average and set state to standing. + _averageUserHeightSensorSpace = _userHeight.get(); + _tippingPoint = _userHeight.get(); + setIsInSittingState(false); } } @@ -692,9 +704,9 @@ void MyAvatar::update(float deltaTime) { } float angleSpine2 = glm::dot(upSpine2, glm::vec3(0.0f, 1.0f, 0.0f)); - if (getControllerPoseInAvatarFrame(controller::Action::HEAD).getTranslation().y < (headDefaultPositionAvatarSpace.y - SQUAT_THRESHOLD) && - (angleSpine2 > COSINE_THIRTY_DEGREES) && - (getUserRecenterModel() != MyAvatar::SitStandModelType::ForceStand)) { + if (getHMDCrouchRecenterEnabled() && + getControllerPoseInAvatarFrame(controller::Action::HEAD).getTranslation().y < (headDefaultPositionAvatarSpace.y - SQUAT_THRESHOLD) && + (angleSpine2 > COSINE_THIRTY_DEGREES)) { _squatTimer += deltaTime; if (_squatTimer > SQUATTY_TIMEOUT) { @@ -832,7 +844,7 @@ void MyAvatar::recalculateChildCauterization() const { _cauterizationNeedsUpdate = true; } -bool MyAvatar::isFollowActive(FollowHelper::FollowType followType) const { +bool MyAvatar::isFollowActive(CharacterController::FollowType followType) const { return _follow.isActive(followType); } @@ -1277,6 +1289,10 @@ void MyAvatar::resizeAvatarEntitySettingHandles(uint32_t maxIndex) { void MyAvatar::saveData() { _dominantHandSetting.set(getDominantHand()); + _allowAvatarStandingPreferenceSetting.set( + allowAvatarStandingPreferenceStrings[static_cast(getAllowAvatarStandingPreference())]); + _allowAvatarLeaningPreferenceSetting.set( + allowAvatarLeaningPreferenceStrings[static_cast(getAllowAvatarLeaningPreference())]); _strafeEnabledSetting.set(getStrafeEnabled()); _hmdAvatarAlignmentTypeSetting.set(getHmdAvatarAlignmentType()); _headPitchSetting.set(getHead()->getBasePitch()); @@ -1311,7 +1327,10 @@ void MyAvatar::saveData() { _analogWalkSpeedSetting.set(getAnalogWalkSpeed()); _analogPlusWalkSpeedSetting.set(getAnalogPlusWalkSpeed()); _controlSchemeIndexSetting.set(getControlSchemeIndex()); - _userRecenterModelSetting.set(userRecenterModelToString(getUserRecenterModel())); + _allowAvatarStandingPreferenceSetting.set( + allowAvatarStandingPreferenceStrings[static_cast(getAllowAvatarStandingPreference())]); + _allowAvatarLeaningPreferenceSetting.set( + allowAvatarLeaningPreferenceStrings[static_cast(getAllowAvatarLeaningPreference())]); auto hmdInterface = DependencyManager::get(); saveAvatarEntityDataToSettings(); @@ -2004,7 +2023,10 @@ void MyAvatar::loadData() { setUserHeight(_userHeightSetting.get(DEFAULT_AVATAR_HEIGHT)); setTargetScale(_scaleSetting.get()); - setUserRecenterModel(stringToUserRecenterModel(_userRecenterModelSetting.get(USER_RECENTER_MODEL_AUTO))); + setAllowAvatarStandingPreference(stringToAllowAvatarStandingPreference(_allowAvatarStandingPreferenceSetting.get( + allowAvatarStandingPreferenceStrings[static_cast(AllowAvatarStandingPreference::Default)]))); + setAllowAvatarLeaningPreference(stringToAllowAvatarLeaningPreference(_allowAvatarLeaningPreferenceSetting.get( + allowAvatarLeaningPreferenceStrings[static_cast(AllowAvatarLeaningPreference::Default)]))); setEnableMeshVisible(Menu::getInstance()->isOptionChecked(MenuOption::MeshVisible)); _follow.setToggleHipsFollowing (Menu::getInstance()->isOptionChecked(MenuOption::ToggleHipsFollowing)); @@ -2666,15 +2688,8 @@ controller::Pose MyAvatar::getControllerPoseInAvatarFrame(controller::Action act } } -glm::quat MyAvatar::getOffHandRotation() const { - auto hand = (getDominantHand() == DOMINANT_RIGHT_HAND) ? controller::Action::LEFT_HAND : controller::Action::RIGHT_HAND; - auto pose = getControllerPoseInAvatarFrame(hand); - return pose.rotation; -} - void MyAvatar::updateMotors() { _characterController.clearMotors(); - glm::quat motorRotation; const float FLYING_MOTOR_TIMESCALE = 0.05f; const float WALKING_MOTOR_TIMESCALE = 0.2f; @@ -2693,35 +2708,17 @@ void MyAvatar::updateMotors() { } if (_motionBehaviors & AVATAR_MOTION_ACTION_MOTOR_ENABLED) { - if (_characterController.getState() == CharacterController::State::Hover || - _characterController.computeCollisionMask() == BULLET_COLLISION_MASK_COLLISIONLESS) { - CameraMode mode = qApp->getCamera().getMode(); - if (!qApp->isHMDMode() && (mode == CAMERA_MODE_FIRST_PERSON_LOOK_AT || mode == CAMERA_MODE_LOOK_AT || mode == CAMERA_MODE_SELFIE)) { - motorRotation = getLookAtRotation(); - } else { - motorRotation = getMyHead()->getHeadOrientation(); - } - } else { - // non-hovering = walking: follow camera twist about vertical but not lift - // we decompose camera's rotation and store the twist part in motorRotation - // however, we need to perform the decomposition in the avatar-frame - // using the local UP axis and then transform back into world-frame - glm::quat orientation = getWorldOrientation(); - glm::quat headOrientation = glm::inverse(orientation) * getMyHead()->getHeadOrientation(); // avatar-frame - glm::quat liftRotation; - swingTwistDecomposition(headOrientation, Vectors::UNIT_Y, liftRotation, motorRotation); - motorRotation = orientation * motorRotation; - } - if (_isPushing || _isBraking || !_isBeingPushed) { - _characterController.addMotor(_actionMotorVelocity, motorRotation, horizontalMotorTimescale, verticalMotorTimescale); + _characterController.addMotor(_actionMotorVelocity, Quaternions::IDENTITY, horizontalMotorTimescale, + verticalMotorTimescale); } else { // _isBeingPushed must be true --> disable action motor by giving it a long timescale, // otherwise it's attempt to "stand in in place" could defeat scripted motor/thrusts - _characterController.addMotor(_actionMotorVelocity, motorRotation, INVALID_MOTOR_TIMESCALE); + _characterController.addMotor(_actionMotorVelocity, Quaternions::IDENTITY, INVALID_MOTOR_TIMESCALE); } } if (_motionBehaviors & AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED) { + glm::quat motorRotation; if (_scriptedMotorFrame == SCRIPTED_MOTOR_CAMERA_FRAME) { motorRotation = getMyHead()->getHeadOrientation() * glm::angleAxis(PI, Vectors::UNIT_Y); } else if (_scriptedMotorFrame == SCRIPTED_MOTOR_AVATAR_FRAME) { @@ -3793,54 +3790,115 @@ glm::vec3 MyAvatar::scaleMotorSpeed(const glm::vec3 forward, const glm::vec3 rig } } -glm::vec3 MyAvatar::calculateScaledDirection(){ +// Calculate the world-space motor velocity for the avatar. +glm::vec3 MyAvatar::calculateScaledDirection() { CharacterController::State state = _characterController.getState(); // compute action input // Determine if we're head or controller relative... - glm::vec3 forward, right; + glm::vec3 forward; + glm::vec3 right; - if (qApp->isHMDMode()) { - auto handRotation = getOffHandRotation(); - glm::vec3 controllerForward(0.0f, 1.0f, 0.0f); - glm::vec3 controllerRight(0.0f, 0.0f, (getDominantHand() == DOMINANT_RIGHT_HAND ? 1.0f : -1.0f)); - glm::vec3 transform; - switch (getMovementReference()) { - case LocomotionRelativeMovementMode::MOVEMENT_HAND_RELATIVE: - forward = (handRotation * controllerForward); - right = (handRotation * controllerRight); - break; - case LocomotionRelativeMovementMode::MOVEMENT_HAND_RELATIVE_LEVELED: - forward = (handRotation * controllerForward); - transform = forward - (glm::dot(forward, Vectors::UNIT_Y) * Vectors::UNIT_Y); - if (glm::length(transform) > EPSILON) { - forward = glm::normalize(transform); - } else { - forward = Vectors::ZERO; - } - right = (handRotation * controllerRight); - transform = right - (glm::dot(right, Vectors::UNIT_Y) * Vectors::UNIT_Y); - if (glm::length(transform) > EPSILON) { - right = glm::normalize(transform); - } else { - right = Vectors::ZERO; - } - break; - case LocomotionRelativeMovementMode::MOVEMENT_HMD_RELATIVE: - default: - forward = IDENTITY_FORWARD; - right = IDENTITY_RIGHT; + const int movementReference = getMovementReference(); + CameraMode cameraMode = qApp->getCamera().getMode(); + + bool vectorsAreInAvatarFrame = true; + bool removeLocalYComponent = false; + + const bool HMDHandRelativeMovement = + qApp->isHMDMode() && (movementReference == LocomotionRelativeMovementMode::MOVEMENT_HAND_RELATIVE || + movementReference == LocomotionRelativeMovementMode::MOVEMENT_HAND_RELATIVE_LEVELED); + + const bool desktopLookatOrSelfieMode = + !qApp->isHMDMode() && (cameraMode == CAMERA_MODE_FIRST_PERSON_LOOK_AT || cameraMode == CAMERA_MODE_LOOK_AT || + cameraMode == CAMERA_MODE_SELFIE); + + const bool hoveringOrCollisionless = _characterController.getState() == CharacterController::State::Hover || + _characterController.computeCollisionMask() == BULLET_COLLISION_MASK_COLLISIONLESS; + + if (HMDHandRelativeMovement) { + const controller::Action directionHand = + (getDominantHand() == DOMINANT_RIGHT_HAND) ? controller::Action::LEFT_HAND : controller::Action::RIGHT_HAND; + const controller::Pose handPoseInAvatarFrame = getControllerPoseInAvatarFrame(directionHand); + + if (handPoseInAvatarFrame.isValid()) { + const glm::vec3 controllerForward(0.0f, 1.0f, 0.0f); + const glm::vec3 controllerRight(0.0f, 0.0f, (directionHand == controller::Action::LEFT_HAND) ? 1.0f : -1.0f); + + forward = (handPoseInAvatarFrame.rotation * controllerForward); + right = (handPoseInAvatarFrame.rotation * controllerRight); + + removeLocalYComponent = (movementReference == LocomotionRelativeMovementMode::MOVEMENT_HAND_RELATIVE_LEVELED); + } + } else { // MOVEMENT_HMD_RELATIVE or desktop mode + if (qApp->isHMDMode()) { + forward = -IDENTITY_FORWARD; + right = -IDENTITY_RIGHT; + } else { + forward = IDENTITY_FORWARD; + right = IDENTITY_RIGHT; + } + + glm::quat rotation = Quaternions::IDENTITY; + + if (hoveringOrCollisionless && desktopLookatOrSelfieMode) { + rotation = getLookAtRotation(); + removeLocalYComponent = false; + vectorsAreInAvatarFrame = false; + } else { + controller::Pose headPoseLocal = getControllerPoseInAvatarFrame(controller::Action::HEAD); + if (headPoseLocal.isValid()) { + rotation = headPoseLocal.rotation; + } + removeLocalYComponent = !hoveringOrCollisionless; + } + + forward = rotation * forward; + right = rotation * right; + } + + if (removeLocalYComponent) { + assert(vectorsAreInAvatarFrame); + + auto removeYAndNormalize = [](glm::vec3& vector) { + vector.y = 0.f; + // Normalize if the remaining components are large enough to get a reliable direction. + const float length = glm::length(vector); + const float MIN_LENGTH_FOR_NORMALIZE = 0.061f; // sin(3.5 degrees) + if (length > MIN_LENGTH_FOR_NORMALIZE) { + vector /= length; + } else { + vector = Vectors::ZERO; + } + }; + + removeYAndNormalize(forward); + removeYAndNormalize(right); + } + + // In HMD, we combine the head pitch into the flying direction even when using hand-relative movement. + // Todo: Option to ignore head pitch in hand-relative flying (MOVEMENT_HAND_RELATIVE_LEVELED would then act like MOVEMENT_HAND_RELATIVE when flying). + if (HMDHandRelativeMovement && hoveringOrCollisionless) { + controller::Pose headPoseLocal = getControllerPoseInAvatarFrame(controller::Action::HEAD); + + if (headPoseLocal.isValid()) { + glm::quat headLocalPitchRotation; + glm::quat headLocalYawRotation_unused; + swingTwistDecomposition(headPoseLocal.rotation, Vectors::UP, headLocalPitchRotation, headLocalYawRotation_unused); + + forward = headLocalPitchRotation * forward; + right = headLocalPitchRotation * right; } - } else { - forward = IDENTITY_FORWARD; - right = IDENTITY_RIGHT; } glm::vec3 direction = scaleMotorSpeed(forward, right); - if (state == CharacterController::State::Hover || - _characterController.computeCollisionMask() == BULLET_COLLISION_MASK_COLLISIONLESS) { - glm::vec3 up = (getDriveKey(TRANSLATE_Y)) * IDENTITY_UP; + if (vectorsAreInAvatarFrame) { + direction = getWorldOrientation() * direction; + } + + if (hoveringOrCollisionless) { + glm::vec3 up = getDriveKey(TRANSLATE_Y) * IDENTITY_UP; direction += up; } @@ -4724,7 +4782,9 @@ void MyAvatar::triggerRotationRecenter() { } // old school meat hook style -glm::mat4 MyAvatar::deriveBodyFromHMDSensor() const { +// forceFollowYPos (default false): true to force the body matrix to be affected by the HMD's +// vertical position, even if crouch recentering is disabled. +glm::mat4 MyAvatar::deriveBodyFromHMDSensor(const bool forceFollowYPos) const { glm::vec3 headPosition(0.0f, _userHeight.get(), 0.0f); glm::quat headOrientation; auto headPose = getControllerPoseInSensorFrame(controller::Action::HEAD); @@ -4760,8 +4820,30 @@ glm::mat4 MyAvatar::deriveBodyFromHMDSensor() const { float invSensorToWorldScale = getUserEyeHeight() / getEyeHeight(); glm::vec3 bodyPos = headPosition + invSensorToWorldScale * (headToNeck + neckToRoot); + glm::quat bodyQuat; - return createMatFromQuatAndPos(headOrientationYawOnly, bodyPos); + const controller::Pose hipsControllerPose = getControllerPoseInSensorFrame(controller::Action::HIPS); + if (hipsControllerPose.isValid()) { + const glm::quat hipsOrientation = hipsControllerPose.rotation * Quaternions::Y_180; + const glm::quat hipsOrientationYawOnly = cancelOutRollAndPitch(hipsOrientation); + + const glm::vec3 hipsPos = hipsControllerPose.getTranslation(); + bodyPos.x = hipsPos.x; + bodyPos.z = hipsPos.z; + + bodyQuat = hipsOrientationYawOnly; + } else { + bodyQuat = headOrientationYawOnly; + } + + if (!forceFollowYPos && !getHMDCrouchRecenterEnabled()) { + // Set the body's vertical position as if it were standing in its T-pose. + bodyPos.y = rig.getUnscaledHipsHeight(); + } + + glm::mat4 bodyMat = createMatFromQuatAndPos(bodyQuat, bodyPos); + + return bodyMat; } glm::mat4 MyAvatar::getSpine2RotationRigSpace() const { @@ -5159,16 +5241,19 @@ bool MyAvatar::getIsInWalkingState() const { return _isInWalkingState; } +// Determine if the user is sitting in the real world. bool MyAvatar::getIsInSittingState() const { return _isInSittingState.get(); } -MyAvatar::SitStandModelType MyAvatar::getUserRecenterModel() const { - return _userRecenterModel.get(); +// Get the user preference of when MyAvatar may stand. +MyAvatar::AllowAvatarStandingPreference MyAvatar::getAllowAvatarStandingPreference() const { + return _allowAvatarStandingPreference.get(); } -bool MyAvatar::getIsSitStandStateLocked() const { - return _lockSitStandState.get(); +// Get the user preference of when MyAvatar may lean. +MyAvatar::AllowAvatarLeaningPreference MyAvatar::getAllowAvatarLeaningPreference() const { + return _allowAvatarLeaningPreference.get(); } float MyAvatar::getWalkSpeed() const { @@ -5221,59 +5306,29 @@ void MyAvatar::setIsInWalkingState(bool isWalking) { _isInWalkingState = isWalking; } +// Specify whether the user is sitting or standing in the real world. void MyAvatar::setIsInSittingState(bool isSitting) { _sitStandStateTimer = 0.0f; _squatTimer = 0.0f; // on reset height we need the count to be more than one in case the user sits and stands up quickly. _isInSittingState.set(isSitting); setResetMode(true); - if (isSitting) { - setCenterOfGravityModelEnabled(false); - } else { - setCenterOfGravityModelEnabled(true); - } + setCenterOfGravityModelEnabled(!isSitting); setSitStandStateChange(true); } -void MyAvatar::setUserRecenterModel(MyAvatar::SitStandModelType modelName) { +// Set the user preference of when the avatar may stand. +void MyAvatar::setAllowAvatarStandingPreference(const MyAvatar::AllowAvatarStandingPreference preference) { + _allowAvatarStandingPreference.set(preference); - _userRecenterModel.set(modelName); - - switch (modelName) { - case MyAvatar::SitStandModelType::ForceSit: - setHMDLeanRecenterEnabled(true); - setIsInSittingState(true); - setIsSitStandStateLocked(true); - break; - case MyAvatar::SitStandModelType::ForceStand: - setHMDLeanRecenterEnabled(true); - setIsInSittingState(false); - setIsSitStandStateLocked(true); - break; - case MyAvatar::SitStandModelType::Auto: - default: - setHMDLeanRecenterEnabled(true); - setIsInSittingState(false); - setIsSitStandStateLocked(false); - break; - case MyAvatar::SitStandModelType::DisableHMDLean: - setHMDLeanRecenterEnabled(false); - setIsInSittingState(false); - setIsSitStandStateLocked(false); - break; - } + // Set the correct vertical position for the avatar body relative to the HMD, + // according to the newly-selected avatar standing preference. + centerBody(false); } -void MyAvatar::setIsSitStandStateLocked(bool isLocked) { - _lockSitStandState.set(isLocked); - _sitStandStateTimer = 0.0f; - _squatTimer = 0.0f; - _averageUserHeightSensorSpace = _userHeight.get(); - _tippingPoint = _userHeight.get(); - if (!isLocked) { - // always start the auto transition mode in standing state. - setIsInSittingState(false); - } +// Set the user preference of when the avatar may lean. +void MyAvatar::setAllowAvatarLeaningPreference(const MyAvatar::AllowAvatarLeaningPreference preference) { + _allowAvatarLeaningPreference.set(preference); } void MyAvatar::setWalkSpeed(float value) { @@ -5402,10 +5457,12 @@ float MyAvatar::getAnalogPlusSprintSpeed() const { return _analogPlusSprintSpeed.get(); } +// Indicate whether the user's real-world sit/stand state has changed or not. void MyAvatar::setSitStandStateChange(bool stateChanged) { _sitStandStateChange = stateChanged; } +// Determine if the user's real-world sit/stand state has changed. float MyAvatar::getSitStandStateChange() const { return _sitStandStateChange; } @@ -5499,65 +5556,82 @@ MyAvatar::FollowHelper::FollowHelper() { } void MyAvatar::FollowHelper::deactivate() { - for (int i = 0; i < NumFollowTypes; i++) { - deactivate((FollowType)i); + for (uint i = 0, e = static_cast(CharacterController::FollowType::Count); i < e; ++i) { + deactivate((CharacterController::FollowType)i); } } -void MyAvatar::FollowHelper::deactivate(FollowType type) { - assert(type >= 0 && type < NumFollowTypes); +void MyAvatar::FollowHelper::deactivate(CharacterController::FollowType type) { + assert(static_cast(type) >= 0 && type < CharacterController::FollowType::Count); _timeRemaining[(int)type] = 0.0f; } -void MyAvatar::FollowHelper::activate(FollowType type) { - assert(type >= 0 && type < NumFollowTypes); +// snapFollow: true to snap immediately to the desired transform with regard to 'type', +// eg. activate(FollowType::Rotation, true) snaps the FollowHelper's rotation immediately +// to the rotation of its _followDesiredBodyTransform. +void MyAvatar::FollowHelper::activate(CharacterController::FollowType type, const bool snapFollow) { + assert(static_cast(type) >= 0 && type < CharacterController::FollowType::Count); + // TODO: Perhaps, the follow time should be proportional to the displacement. - _timeRemaining[(int)type] = FOLLOW_TIME; + _timeRemaining[(int)type] = snapFollow ? CharacterController::FOLLOW_TIME_IMMEDIATE_SNAP : FOLLOW_TIME; } -bool MyAvatar::FollowHelper::isActive(FollowType type) const { - assert(type >= 0 && type < NumFollowTypes); +bool MyAvatar::FollowHelper::isActive(CharacterController::FollowType type) const { + assert(static_cast(type) >= 0 && type < CharacterController::FollowType::Count); return _timeRemaining[(int)type] > 0.0f; } bool MyAvatar::FollowHelper::isActive() const { - for (int i = 0; i < NumFollowTypes; i++) { - if (isActive((FollowType)i)) { + for (uint i = 0, e = static_cast(CharacterController::FollowType::Count); i < e; ++i) { + if (isActive((CharacterController::FollowType)i)) { return true; } } return false; } -float MyAvatar::FollowHelper::getMaxTimeRemaining() const { - float max = 0.0f; - for (int i = 0; i < NumFollowTypes; i++) { - if (_timeRemaining[i] > max) { - max = _timeRemaining[i]; +void MyAvatar::FollowHelper::decrementTimeRemaining(float dt) { + for (uint i = 0, e = static_cast(CharacterController::FollowType::Count); i < e; ++i) { + if (_timeRemaining[i] == CharacterController::FOLLOW_TIME_IMMEDIATE_SNAP) { + _timeRemaining[i] = 0.f; + } + else { + _timeRemaining[i] -= dt; } } - return max; } -void MyAvatar::FollowHelper::decrementTimeRemaining(float dt) { - for (int i = 0; i < NumFollowTypes; i++) { - _timeRemaining[i] -= dt; +// shouldSnapOut: (out) true if the FollowHelper should snap immediately to its desired rotation. +bool MyAvatar::FollowHelper::shouldActivateRotation(const MyAvatar& myAvatar, + const glm::mat4& desiredBodyMatrix, + const glm::mat4& currentBodyMatrix, + bool& shouldSnapOut) const { + shouldSnapOut = false; + + // If hips are under direct control (tracked), they give our desired body rotation and we snap to it every frame. + if (myAvatar.areHipsTracked()) { + shouldSnapOut = true; + return true; } -} -bool MyAvatar::FollowHelper::shouldActivateRotation(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const { const float FOLLOW_ROTATION_THRESHOLD = cosf(myAvatar.getRotationThreshold()); glm::vec2 bodyFacing = getFacingDir2D(currentBodyMatrix); return glm::dot(-myAvatar.getHeadControllerFacingMovingAverage(), bodyFacing) < FOLLOW_ROTATION_THRESHOLD; } bool MyAvatar::FollowHelper::shouldActivateHorizontal(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const { + if (!myAvatar.isAllowedToLean()) { + controller::Pose currentHeadPose = myAvatar.getControllerPoseInAvatarFrame(controller::Action::HEAD); + if (!withinBaseOfSupport(currentHeadPose)) { + return true; + } + } + // -z axis of currentBodyMatrix in world space. glm::vec3 forward = glm::normalize(glm::vec3(-currentBodyMatrix[0][2], -currentBodyMatrix[1][2], -currentBodyMatrix[2][2])); // x axis of currentBodyMatrix in world space. glm::vec3 right = glm::normalize(glm::vec3(currentBodyMatrix[0][0], currentBodyMatrix[1][0], currentBodyMatrix[2][0])); glm::vec3 offset = extractTranslation(desiredBodyMatrix) - extractTranslation(currentBodyMatrix); - controller::Pose currentHeadPose = myAvatar.getControllerPoseInAvatarFrame(controller::Action::HEAD); float forwardLeanAmount = glm::dot(forward, offset); float lateralLeanAmount = glm::dot(right, offset); @@ -5567,11 +5641,7 @@ bool MyAvatar::FollowHelper::shouldActivateHorizontal(const MyAvatar& myAvatar, const float MAX_BACKWARD_LEAN = 0.1f; bool stepDetected = false; - if (myAvatar.getIsInSittingState()) { - if (!withinBaseOfSupport(currentHeadPose)) { - stepDetected = true; - } - } else if (forwardLeanAmount > 0 && forwardLeanAmount > MAX_FORWARD_LEAN) { + if (forwardLeanAmount > MAX_FORWARD_LEAN) { stepDetected = true; } else if (forwardLeanAmount < 0 && forwardLeanAmount < -MAX_BACKWARD_LEAN) { stepDetected = true; @@ -5583,50 +5653,60 @@ bool MyAvatar::FollowHelper::shouldActivateHorizontal(const MyAvatar& myAvatar, bool MyAvatar::FollowHelper::shouldActivateHorizontalCG(MyAvatar& myAvatar) const { - // get the current readings - controller::Pose currentHeadPose = myAvatar.getControllerPoseInAvatarFrame(controller::Action::HEAD); - controller::Pose currentLeftHandPose = myAvatar.getControllerPoseInAvatarFrame(controller::Action::LEFT_HAND); - controller::Pose currentRightHandPose = myAvatar.getControllerPoseInAvatarFrame(controller::Action::RIGHT_HAND); - controller::Pose currentHeadSensorPose = myAvatar.getControllerPoseInSensorFrame(controller::Action::HEAD); - - bool stepDetected = false; - float myScale = myAvatar.getAvatarScale(); - if (myAvatar.getIsInWalkingState()) { - stepDetected = true; - } else { - if (!withinBaseOfSupport(currentHeadPose) && - headAngularVelocityBelowThreshold(currentHeadPose) && - isWithinThresholdHeightMode(currentHeadSensorPose, myAvatar.getCurrentStandingHeight(), myScale) && - handDirectionMatchesHeadDirection(currentLeftHandPose, currentRightHandPose, currentHeadPose) && - handAngularVelocityBelowThreshold(currentLeftHandPose, currentRightHandPose) && - headVelocityGreaterThanThreshold(currentHeadPose) && - isHeadLevel(currentHeadPose, myAvatar.getAverageHeadRotation())) { - // a step is detected + return true; + } + + controller::Pose currentHeadPose = myAvatar.getControllerPoseInAvatarFrame(controller::Action::HEAD); + bool stepDetected = false; + + if (!withinBaseOfSupport(currentHeadPose)) { + if (!myAvatar.isAllowedToLean()) { stepDetected = true; - if (glm::length(currentHeadPose.velocity) > DEFAULT_AVATAR_WALK_SPEED_THRESHOLD) { - myAvatar.setIsInWalkingState(true); - } } else { - glm::vec3 defaultHipsPosition = myAvatar.getAbsoluteDefaultJointTranslationInObjectFrame(myAvatar.getJointIndex("Hips")); - glm::vec3 defaultHeadPosition = myAvatar.getAbsoluteDefaultJointTranslationInObjectFrame(myAvatar.getJointIndex("Head")); - glm::vec3 currentHeadPosition = currentHeadPose.getTranslation(); - float anatomicalHeadToHipsDistance = glm::length(defaultHeadPosition - defaultHipsPosition); - if (!isActive(Horizontal) && - (!isActive(Vertical)) && - (glm::length(currentHeadPosition - defaultHipsPosition) > (anatomicalHeadToHipsDistance + (DEFAULT_AVATAR_SPINE_STRETCH_LIMIT * anatomicalHeadToHipsDistance)))) { - myAvatar.setResetMode(true); + float myScale = myAvatar.getAvatarScale(); + + // get the current readings + controller::Pose currentLeftHandPose = myAvatar.getControllerPoseInAvatarFrame(controller::Action::LEFT_HAND); + controller::Pose currentRightHandPose = myAvatar.getControllerPoseInAvatarFrame(controller::Action::RIGHT_HAND); + controller::Pose currentHeadSensorPose = myAvatar.getControllerPoseInSensorFrame(controller::Action::HEAD); + + if (headAngularVelocityBelowThreshold(currentHeadPose) && + isWithinThresholdHeightMode(currentHeadSensorPose, myAvatar.getCurrentStandingHeight(), myScale) && + handDirectionMatchesHeadDirection(currentLeftHandPose, currentRightHandPose, currentHeadPose) && + handAngularVelocityBelowThreshold(currentLeftHandPose, currentRightHandPose) && + headVelocityGreaterThanThreshold(currentHeadPose) && + isHeadLevel(currentHeadPose, myAvatar.getAverageHeadRotation())) { + // a step is detected stepDetected = true; - if (glm::length(currentHeadPose.velocity) > DEFAULT_AVATAR_WALK_SPEED_THRESHOLD) { - myAvatar.setIsInWalkingState(true); - } } } } + + if (!stepDetected) { + glm::vec3 defaultHipsPosition = myAvatar.getAbsoluteDefaultJointTranslationInObjectFrame(myAvatar.getJointIndex("Hips")); + glm::vec3 defaultHeadPosition = myAvatar.getAbsoluteDefaultJointTranslationInObjectFrame(myAvatar.getJointIndex("Head")); + glm::vec3 currentHeadPosition = currentHeadPose.getTranslation(); + float anatomicalHeadToHipsDistance = glm::length(defaultHeadPosition - defaultHipsPosition); + if (!isActive(CharacterController::FollowType::Horizontal) && (!isActive(CharacterController::FollowType::Vertical)) && + (glm::length(currentHeadPosition - defaultHipsPosition) > (anatomicalHeadToHipsDistance + (DEFAULT_AVATAR_SPINE_STRETCH_LIMIT * anatomicalHeadToHipsDistance)))) { + myAvatar.setResetMode(true); + stepDetected = true; + } + } + + if (stepDetected) { + if (glm::length(currentHeadPose.velocity) > DEFAULT_AVATAR_WALK_SPEED_THRESHOLD) { + myAvatar.setIsInWalkingState(true); + } + } + return stepDetected; } -bool MyAvatar::FollowHelper::shouldActivateVertical(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const { +bool MyAvatar::FollowHelper::shouldActivateVertical(const MyAvatar& myAvatar, + const glm::mat4& desiredBodyMatrix, + const glm::mat4& currentBodyMatrix) const { const float CYLINDER_TOP = 2.0f; const float CYLINDER_BOTTOM = -1.5f; const float SITTING_BOTTOM = -0.02f; @@ -5638,9 +5718,6 @@ bool MyAvatar::FollowHelper::shouldActivateVertical(const MyAvatar& myAvatar, co returnValue = true; } else { if (myAvatar.getIsInSittingState()) { - if (myAvatar.getIsSitStandStateLocked()) { - returnValue = (offset.y > CYLINDER_TOP); - } if (offset.y < SITTING_BOTTOM) { // we recenter more easily when in sitting state. returnValue = true; @@ -5657,51 +5734,80 @@ bool MyAvatar::FollowHelper::shouldActivateVertical(const MyAvatar& myAvatar, co return returnValue; } -void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, - const glm::mat4& currentBodyMatrix, bool hasDriveInput) { +void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, + const glm::mat4& desiredBodyMatrix, + const glm::mat4& currentBodyMatrix, + bool hasDriveInput) { + const bool feetAreTracked = myAvatar.areFeetTracked(); - if (myAvatar.getHMDLeanRecenterEnabled() && - qApp->getCamera().getMode() != CAMERA_MODE_MIRROR) { - if (!isActive(Rotation) && (shouldActivateRotation(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) { - activate(Rotation); - myAvatar.setHeadControllerFacingMovingAverage(myAvatar.getHeadControllerFacing()); - } - if (myAvatar.getCenterOfGravityModelEnabled()) { - if (!isActive(Horizontal) && (shouldActivateHorizontalCG(myAvatar) || hasDriveInput)) { - activate(Horizontal); - if (myAvatar.getEnableStepResetRotation()) { - activate(Rotation); - myAvatar.setHeadControllerFacingMovingAverage(myAvatar.getHeadControllerFacing()); - } + if (myAvatar.getHMDLeanRecenterEnabled()) { + + // Rotation recenter + + { + bool snapFollow = false; + if (!isActive(CharacterController::FollowType::Rotation) && + (shouldActivateRotation(myAvatar, desiredBodyMatrix, currentBodyMatrix, snapFollow) || hasDriveInput)) { + activate(CharacterController::FollowType::Rotation, snapFollow); + myAvatar.setHeadControllerFacingMovingAverage(myAvatar.getHeadControllerFacing()); } + } + + // Horizontal and rotation recenter + + if ((feetAreTracked || getForceActivateHorizontal()) && !isActive(CharacterController::FollowType::Horizontal)) { + activate(CharacterController::FollowType::Horizontal, feetAreTracked); + setForceActivateHorizontal(false); } else { - // center of gravity model is not enabled - if (!isActive(Horizontal) && (shouldActivateHorizontal(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) { - activate(Horizontal); - if (myAvatar.getEnableStepResetRotation() && !myAvatar.getIsInSittingState()) { - activate(Rotation); - myAvatar.setHeadControllerFacingMovingAverage(myAvatar.getHeadControllerFacing()); + if ((myAvatar.getAllowAvatarLeaningPreference() != MyAvatar::AllowAvatarLeaningPreference::AlwaysNoRecenter) && + qApp->getCamera().getMode() != CAMERA_MODE_MIRROR) { + if (myAvatar.getCenterOfGravityModelEnabled()) { + if (!isActive(CharacterController::FollowType::Horizontal) && (shouldActivateHorizontalCG(myAvatar) || hasDriveInput)) { + activate(CharacterController::FollowType::Horizontal, false); + if (myAvatar.getEnableStepResetRotation()) { + activate(CharacterController::FollowType::Rotation, false); + myAvatar.setHeadControllerFacingMovingAverage(myAvatar.getHeadControllerFacing()); + } + } + } else { + // center of gravity model is not enabled + if (!isActive(CharacterController::FollowType::Horizontal) && + (shouldActivateHorizontal(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) { + activate(CharacterController::FollowType::Horizontal, false); + if (myAvatar.getEnableStepResetRotation() && !myAvatar.getIsInSittingState()) { + activate(CharacterController::FollowType::Rotation, false); + myAvatar.setHeadControllerFacingMovingAverage(myAvatar.getHeadControllerFacing()); + } + } } } } - if (!isActive(Vertical) && (shouldActivateVertical(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) { - activate(Vertical); - if (_squatDetected) { - _squatDetected = false; + + // Vertical recenter + + if (myAvatar.getHMDCrouchRecenterEnabled() && qApp->getCamera().getMode() != CAMERA_MODE_MIRROR) { + if (!isActive(CharacterController::FollowType::Vertical) && + (shouldActivateVertical(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) { + activate(CharacterController::FollowType::Vertical, false); + if (_squatDetected) { + _squatDetected = false; + } } } } else { - if (!isActive(Rotation) && getForceActivateRotation()) { - activate(Rotation); + // Forced activations can be requested by MyAvatar::triggerVerticalRecenter, callable from scripts. + + if (!isActive(CharacterController::FollowType::Rotation) && getForceActivateRotation()) { + activate(CharacterController::FollowType::Rotation, true); myAvatar.setHeadControllerFacingMovingAverage(myAvatar.getHeadControllerFacing()); setForceActivateRotation(false); } - if (!isActive(Horizontal) && getForceActivateHorizontal()) { - activate(Horizontal); + if (!isActive(CharacterController::FollowType::Horizontal) && getForceActivateHorizontal()) { + activate(CharacterController::FollowType::Horizontal, true); setForceActivateHorizontal(false); } - if (!isActive(Vertical) && getForceActivateVertical()) { - activate(Vertical); + if (!isActive(CharacterController::FollowType::Vertical) && getForceActivateVertical()) { + activate(CharacterController::FollowType::Vertical, true); setForceActivateVertical(false); } } @@ -5721,21 +5827,21 @@ void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat // remove scale present from sensorToWorldMatrix followWorldPose.scale() = glm::vec3(1.0f); - if (isActive(Rotation)) { - //use the hmd reading for the hips follow - followWorldPose.rot() = glmExtractRotation(desiredWorldMatrix); + if (isActive(CharacterController::FollowType::Rotation)) { + //use the hmd reading for the hips follow + followWorldPose.rot() = glmExtractRotation(desiredWorldMatrix); } - if (isActive(Horizontal)) { + if (isActive(CharacterController::FollowType::Horizontal)) { glm::vec3 desiredTranslation = extractTranslation(desiredWorldMatrix); followWorldPose.trans().x = desiredTranslation.x; followWorldPose.trans().z = desiredTranslation.z; } - if (isActive(Vertical)) { + if (isActive(CharacterController::FollowType::Vertical)) { glm::vec3 desiredTranslation = extractTranslation(desiredWorldMatrix); followWorldPose.trans().y = desiredTranslation.y; } - myAvatar.getCharacterController()->setFollowParameters(followWorldPose, getMaxTimeRemaining()); + myAvatar.getCharacterController()->setFollowParameters(followWorldPose); } glm::mat4 MyAvatar::FollowHelper::postPhysicsUpdate(MyAvatar& myAvatar, const glm::mat4& currentBodyMatrix) { @@ -5755,11 +5861,15 @@ glm::mat4 MyAvatar::FollowHelper::postPhysicsUpdate(MyAvatar& myAvatar, const gl glm::mat4 newBodyMat = createMatFromQuatAndPos(sensorAngularDisplacement * glmExtractRotation(currentBodyMatrix), sensorLinearDisplacement + extractTranslation(currentBodyMatrix)); - if (myAvatar.getSitStandStateChange()) { - myAvatar.setSitStandStateChange(false); - deactivate(Vertical); - setTranslation(newBodyMat, extractTranslation(myAvatar.deriveBodyFromHMDSensor())); + + if (myAvatar.getHMDCrouchRecenterEnabled()) { + if (myAvatar.getSitStandStateChange()) { + myAvatar.setSitStandStateChange(false); + deactivate(CharacterController::FollowType::Vertical); + setTranslation(newBodyMat, extractTranslation(myAvatar.deriveBodyFromHMDSensor())); + } } + return newBodyMat; } else { return currentBodyMatrix; @@ -6127,7 +6237,7 @@ void MyAvatar::updateHoldActions(const AnimPose& prePhysicsPose, const AnimPose& } bool MyAvatar::isRecenteringHorizontally() const { - return _follow.isActive(FollowHelper::Horizontal); + return _follow.isActive(CharacterController::FollowType::Horizontal); } const MyHead* MyAvatar::getMyHead() const { @@ -6583,7 +6693,7 @@ void MyAvatar::beginSit(const glm::vec3& position, const glm::quat& rotation) { setHMDLeanRecenterEnabled(false); // Disable movement setSitDriveKeysStatus(false); - centerBody(); + centerBody(true); int hipIndex = getJointIndex("Hips"); clearPinOnJoint(hipIndex); pinJoint(hipIndex, position, rotation); @@ -6601,7 +6711,7 @@ void MyAvatar::endSit(const glm::vec3& position, const glm::quat& rotation) { _characterController.setSeated(false); setCollisionsEnabled(true); setHMDLeanRecenterEnabled(true); - centerBody(); + centerBody(false); slamPosition(position); setWorldOrientation(rotation); @@ -6906,6 +7016,30 @@ bool MyAvatar::isJumping() { _characterController.getState() == CharacterController::State::Takeoff) && !isFlying(); } +// Determine if the avatar is allowed to lean in its current situation. +bool MyAvatar::isAllowedToLean() const { + return (getAllowAvatarLeaningPreference() == MyAvatar::AllowAvatarLeaningPreference::Always) || + ((getAllowAvatarLeaningPreference() == MyAvatar::AllowAvatarLeaningPreference::WhenUserIsStanding) && + !getIsInSittingState()); +} + +// Determine if the feet are under direct control (tracked). +bool MyAvatar::areFeetTracked() const { + // Foot tracking only activates when both feet are tracked, so we only need to test one. + return getControllerPoseInSensorFrame(controller::Action::LEFT_FOOT).isValid(); +} + +// Determine if the hips are under direct control (tracked). +bool MyAvatar::areHipsTracked() const { + return getControllerPoseInSensorFrame(controller::Action::HIPS).isValid(); +} + +// Determine if crouch recentering is enabled (making the avatar stand when the user is sitting in the real world). +bool MyAvatar::getHMDCrouchRecenterEnabled() const { + return (!_characterController.getSeated() && + (_allowAvatarStandingPreference.get() == AllowAvatarStandingPreference::Always) && !areFeetTracked()); +} + bool MyAvatar::setPointAt(const glm::vec3& pointAtTarget) { if (QThread::currentThread() != thread()) { bool result = false; diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 3140c68f88..9e817b5167 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -282,16 +282,11 @@ class MyAvatar : public Avatar { *

Warning: Setting this value also sets the value of analogPlusSprintSpeed to twice * the value.

* @property {number} analogPlusSprintSpeed - The sprint (run) speed of your avatar for the "AnalogPlus" control scheme. - * @property {MyAvatar.SitStandModelType} userRecenterModel - Controls avatar leaning and recentering behavior. - * @property {number} isInSittingState - true if the user wearing the HMD is determined to be sitting - * (avatar leaning is disabled, recentering is enabled), false if the user wearing the HMD is - * determined to be standing (avatar leaning is enabled, and avatar recenters if it leans too far). - * If userRecenterModel == 2 (i.e., "auto") the property value automatically updates as the user sits - * or stands, unless isSitStandStateLocked == true. Setting the property value overrides the current - * sitting / standing state, which is updated when the user next sits or stands unless - * isSitStandStateLocked == true. - * @property {boolean} isSitStandStateLocked - true to lock the avatar sitting/standing state, i.e., use this - * to disable automatically changing state. + * @property {number} isInSittingState - true if the user wearing the HMD is determined to be sitting; + * false if the user wearing the HMD is determined to be standing. This can affect whether the avatar + * is allowed to stand, lean or recenter its footing, depending on user preferences. + * The property value automatically updates as the user sits or stands. Setting the property value overrides the current + * sitting / standing state, which is updated when the user next sits or stands. * @property {boolean} allowTeleporting - true if teleporting is enabled in the Interface settings, * false if it isn't. Read-only. * @@ -413,8 +408,6 @@ class MyAvatar : public Avatar { Q_PROPERTY(float walkBackwardSpeed READ getWalkBackwardSpeed WRITE setWalkBackwardSpeed NOTIFY walkBackwardSpeedChanged); Q_PROPERTY(float sprintSpeed READ getSprintSpeed WRITE setSprintSpeed NOTIFY sprintSpeedChanged); Q_PROPERTY(bool isInSittingState READ getIsInSittingState WRITE setIsInSittingState); - Q_PROPERTY(MyAvatar::SitStandModelType userRecenterModel READ getUserRecenterModel WRITE setUserRecenterModel); - Q_PROPERTY(bool isSitStandStateLocked READ getIsSitStandStateLocked WRITE setIsSitStandStateLocked); Q_PROPERTY(bool allowTeleporting READ getAllowTeleporting) const QString DOMINANT_LEFT_HAND = "left"; @@ -549,6 +542,31 @@ public: }; Q_ENUM(SitStandModelType) + // Note: The option strings in setupPreferences (PreferencesDialog.cpp) must match this order. + enum class AllowAvatarStandingPreference : uint + { + WhenUserIsStanding, + Always, + Count, + Default = Always, + }; + Q_ENUM(AllowAvatarStandingPreference) + + // Note: The option strings in setupPreferences (PreferencesDialog.cpp) must match this order. + enum class AllowAvatarLeaningPreference : uint + { + WhenUserIsStanding, + Always, + Never, + AlwaysNoRecenter, // experimental + Count, + Default = WhenUserIsStanding, + }; + Q_ENUM(AllowAvatarLeaningPreference) + + static const std::array allowAvatarStandingPreferenceStrings; + static const std::array allowAvatarLeaningPreferenceStrings; + explicit MyAvatar(QThread* thread); virtual ~MyAvatar(); @@ -576,7 +594,7 @@ public: * the HMD. * @function MyAvatar.centerBody */ - Q_INVOKABLE void centerBody(); // thread-safe + Q_INVOKABLE void centerBody(const bool forceFollowYPos); // thread-safe /**jsdoc @@ -1417,7 +1435,6 @@ public: controller::Pose getControllerPoseInSensorFrame(controller::Action action) const; controller::Pose getControllerPoseInWorldFrame(controller::Action action) const; controller::Pose getControllerPoseInAvatarFrame(controller::Action action) const; - glm::quat getOffHandRotation() const; bool hasDriveInput() const; @@ -1709,7 +1726,7 @@ public: // derive avatar body position and orientation from the current HMD Sensor location. // results are in sensor frame (-z forward) - glm::mat4 deriveBodyFromHMDSensor() const; + glm::mat4 deriveBodyFromHMDSensor(const bool forceFollowYPos = false) const; glm::mat4 getSpine2RotationRigSpace() const; @@ -1753,10 +1770,10 @@ public: bool getIsInWalkingState() const; void setIsInSittingState(bool isSitting); bool getIsInSittingState() const; - void setUserRecenterModel(MyAvatar::SitStandModelType modelName); - MyAvatar::SitStandModelType getUserRecenterModel() const; - void setIsSitStandStateLocked(bool isLocked); - bool getIsSitStandStateLocked() const; + void setAllowAvatarStandingPreference(const AllowAvatarStandingPreference preference); + AllowAvatarStandingPreference getAllowAvatarStandingPreference() const; + void setAllowAvatarLeaningPreference(const AllowAvatarLeaningPreference preference); + AllowAvatarLeaningPreference getAllowAvatarLeaningPreference() const; void setWalkSpeed(float value); float getWalkSpeed() const; void setWalkBackwardSpeed(float value); @@ -1989,6 +2006,10 @@ public: glm::vec3 getLookAtPivotPoint(); glm::vec3 getCameraEyesPosition(float deltaTime); bool isJumping(); + bool getHMDCrouchRecenterEnabled() const; + bool isAllowedToLean() const; + bool areFeetTracked() const; + bool areHipsTracked() const; public slots: @@ -2841,23 +2862,15 @@ private: struct FollowHelper { FollowHelper(); - enum FollowType { - Rotation = 0, - Horizontal, - Vertical, - NumFollowTypes - }; - float _timeRemaining[NumFollowTypes]; + CharacterController::FollowTimePerType _timeRemaining; void deactivate(); - void deactivate(FollowType type); - void activate(); - void activate(FollowType type); + void deactivate(CharacterController::FollowType type); + void activate(CharacterController::FollowType type, const bool snapFollow); bool isActive() const; - bool isActive(FollowType followType) const; - float getMaxTimeRemaining() const; + bool isActive(CharacterController::FollowType followType) const; void decrementTimeRemaining(float dt); - bool shouldActivateRotation(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const; + bool shouldActivateRotation(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix, bool& shouldSnapOut) const; bool shouldActivateVertical(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const; bool shouldActivateHorizontal(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const; bool shouldActivateHorizontalCG(MyAvatar& myAvatar) const; @@ -2880,7 +2893,7 @@ private: FollowHelper _follow; - bool isFollowActive(FollowHelper::FollowType followType) const; + bool isFollowActive(CharacterController::FollowType followType) const; bool _goToPending { false }; bool _physicsSafetyPending { false }; @@ -2922,6 +2935,9 @@ private: bool _centerOfGravityModelEnabled { true }; bool _hmdLeanRecenterEnabled { true }; + bool _hmdCrouchRecenterEnabled{ + true + }; // Is MyAvatar allowed to recenter vertically (stand) when the user is sitting in the real world. bool _sprint { false }; AnimPose _prePhysicsRoomPose; @@ -2953,7 +2969,6 @@ private: ThreadSafeValueCache _userHeight { DEFAULT_AVATAR_HEIGHT }; float _averageUserHeightSensorSpace { _userHeight.get() }; bool _sitStandStateChange { false }; - ThreadSafeValueCache _lockSitStandState { false }; // max unscaled forward movement speed ThreadSafeValueCache _defaultWalkSpeed { DEFAULT_AVATAR_MAX_WALKING_SPEED }; @@ -2969,7 +2984,12 @@ private: float _walkSpeedScalar { AVATAR_WALK_SPEED_SCALAR }; bool _isInWalkingState { false }; ThreadSafeValueCache _isInSittingState { false }; - ThreadSafeValueCache _userRecenterModel { MyAvatar::SitStandModelType::Auto }; + ThreadSafeValueCache _allowAvatarStandingPreference{ + MyAvatar::AllowAvatarStandingPreference::Default + }; // The user preference of when MyAvatar may stand. + ThreadSafeValueCache _allowAvatarLeaningPreference{ + MyAvatar::AllowAvatarLeaningPreference::Default + }; // The user preference of when MyAvatar may lean. float _sitStandStateTimer { 0.0f }; float _squatTimer { 0.0f }; float _tippingPoint { _userHeight.get() }; @@ -3012,7 +3032,8 @@ private: Setting::Handle _controlSchemeIndexSetting; std::vector> _avatarEntityIDSettings; std::vector> _avatarEntityDataSettings; - Setting::Handle _userRecenterModelSetting; + Setting::Handle _allowAvatarStandingPreferenceSetting; + Setting::Handle _allowAvatarLeaningPreferenceSetting; // AvatarEntities stuff: // We cache the "map of unfortunately-formatted-binary-blobs" because they are expensive to compute diff --git a/interface/src/avatar/MyCharacterController.cpp b/interface/src/avatar/MyCharacterController.cpp index 997dcfe685..0d382934b8 100755 --- a/interface/src/avatar/MyCharacterController.cpp +++ b/interface/src/avatar/MyCharacterController.cpp @@ -26,7 +26,9 @@ void MyCharacterController::RayShotgunResult::reset() { walkable = true; } -MyCharacterController::MyCharacterController(std::shared_ptr avatar) { +MyCharacterController::MyCharacterController(std::shared_ptr avatar, + const FollowTimePerType& followTimeRemainingPerType) : + CharacterController(followTimeRemainingPerType) { assert(avatar); _avatar = avatar; diff --git a/interface/src/avatar/MyCharacterController.h b/interface/src/avatar/MyCharacterController.h index eefcc92637..b25c2412a0 100644 --- a/interface/src/avatar/MyCharacterController.h +++ b/interface/src/avatar/MyCharacterController.h @@ -23,7 +23,7 @@ class DetailedMotionState; class MyCharacterController : public CharacterController { public: - explicit MyCharacterController(std::shared_ptr avatar); + explicit MyCharacterController(std::shared_ptr avatar, const FollowTimePerType& followTimeRemainingPerType); ~MyCharacterController (); void addToWorld() override; diff --git a/interface/src/avatar/MySkeletonModel.cpp b/interface/src/avatar/MySkeletonModel.cpp index 6fe199aaba..2d33ee35ca 100755 --- a/interface/src/avatar/MySkeletonModel.cpp +++ b/interface/src/avatar/MySkeletonModel.cpp @@ -65,13 +65,20 @@ static AnimPose computeHipsInSensorFrame(MyAvatar* myAvatar, bool isFlying) { return result; } + const bool useCenterOfGravityModel = + myAvatar->getCenterOfGravityModelEnabled() && !isFlying && !myAvatar->getIsInWalkingState() && + !myAvatar->getIsInSittingState() && myAvatar->getHMDLeanRecenterEnabled() && + (myAvatar->getAllowAvatarLeaningPreference() != MyAvatar::AllowAvatarLeaningPreference::AlwaysNoRecenter) && + myAvatar->getHMDCrouchRecenterEnabled(); + glm::mat4 hipsMat; - if (myAvatar->getCenterOfGravityModelEnabled() && !isFlying && !(myAvatar->getIsInWalkingState()) && !(myAvatar->getIsInSittingState()) && myAvatar->getHMDLeanRecenterEnabled()) { + if (useCenterOfGravityModel) { // then we use center of gravity model hipsMat = myAvatar->deriveBodyUsingCgModel(); - } else { + } + else { // otherwise use the default of putting the hips under the head - hipsMat = myAvatar->deriveBodyFromHMDSensor(); + hipsMat = myAvatar->deriveBodyFromHMDSensor(true); } glm::vec3 hipsPos = extractTranslation(hipsMat); glm::quat hipsRot = glmExtractRotation(hipsMat); @@ -82,7 +89,7 @@ static AnimPose computeHipsInSensorFrame(MyAvatar* myAvatar, bool isFlying) { // dampen hips rotation, by mixing it with the avatar orientation in sensor space // turning this off for center of gravity model because it is already mixed in there - if (!(myAvatar->getCenterOfGravityModelEnabled())) { + if (!useCenterOfGravityModel) { const float MIX_RATIO = 0.5f; hipsRot = safeLerp(glmExtractRotation(avatarToSensorMat), hipsRot, MIX_RATIO); } diff --git a/interface/src/ui/AnimStats.cpp b/interface/src/ui/AnimStats.cpp index 2a355e48d1..fff69cb1c0 100644 --- a/interface/src/ui/AnimStats.cpp +++ b/interface/src/ui/AnimStats.cpp @@ -67,13 +67,13 @@ void AnimStats::updateStats(bool force) { // print if we are recentering or not. _recenterText = "Recenter: "; - if (myAvatar->isFollowActive(MyAvatar::FollowHelper::Rotation)) { + if (myAvatar->isFollowActive(CharacterController::FollowType::Rotation)) { _recenterText += "Rotation "; } - if (myAvatar->isFollowActive(MyAvatar::FollowHelper::Horizontal)) { + if (myAvatar->isFollowActive(CharacterController::FollowType::Horizontal)) { _recenterText += "Horizontal "; } - if (myAvatar->isFollowActive(MyAvatar::FollowHelper::Vertical)) { + if (myAvatar->isFollowActive(CharacterController::FollowType::Vertical)) { _recenterText += "Vertical "; } emit recenterTextChanged(); diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index 79d9ebaa5c..12615426f1 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -422,40 +422,40 @@ void setupPreferences() { preferences->addPreference(preference); } { - auto getter = [myAvatar]()->int { - switch (myAvatar->getUserRecenterModel()) { - case MyAvatar::SitStandModelType::Auto: - default: - return 0; - case MyAvatar::SitStandModelType::ForceSit: - return 1; - case MyAvatar::SitStandModelType::ForceStand: - return 2; - case MyAvatar::SitStandModelType::DisableHMDLean: - return 3; - } + const IntPreference::Getter getter = [myAvatar]() -> int { + return static_cast(myAvatar->getAllowAvatarStandingPreference()); }; - auto setter = [myAvatar](int value) { - switch (value) { - case 0: - default: - myAvatar->setUserRecenterModel(MyAvatar::SitStandModelType::Auto); - break; - case 1: - myAvatar->setUserRecenterModel(MyAvatar::SitStandModelType::ForceSit); - break; - case 2: - myAvatar->setUserRecenterModel(MyAvatar::SitStandModelType::ForceStand); - break; - case 3: - myAvatar->setUserRecenterModel(MyAvatar::SitStandModelType::DisableHMDLean); - break; - } + + const IntPreference::Setter setter = [myAvatar](const int& value) { + myAvatar->setAllowAvatarStandingPreference(static_cast(value)); }; - auto preference = new RadioButtonsPreference(VR_MOVEMENT, "Auto / Force Sit / Force Stand / Disable Recenter", getter, setter); + + auto preference = new RadioButtonsPreference(VR_MOVEMENT, "Allow my avatar to stand", getter, setter); QStringList items; - items << "Auto - turns on avatar leaning when standing in real world" << "Seated - disables all avatar leaning while sitting in real world" << "Standing - enables avatar leaning while sitting in real world" << "Disabled - allows avatar sitting on the floor [Experimental]"; - preference->setHeading("Avatar leaning behavior"); + items << "When I'm standing" + << "Always"; // Must match the order in MyAvatar::AllowAvatarStandingPreference. + assert(items.size() == static_cast(MyAvatar::AllowAvatarStandingPreference::Count)); + preference->setHeading("Allow my avatar to stand:"); + preference->setItems(items); + preferences->addPreference(preference); + } + { + const IntPreference::Getter getter = [myAvatar]() -> int { + return static_cast(myAvatar->getAllowAvatarLeaningPreference()); + }; + + const IntPreference::Setter setter = [myAvatar](const int& value) { + myAvatar->setAllowAvatarLeaningPreference(static_cast(value)); + }; + + auto preference = new RadioButtonsPreference(VR_MOVEMENT, "Allow my avatar to lean", getter, setter); + QStringList items; + items << "When I'm standing" + << "Always" + << "Never" + << "Always, no recenter (Experimental)"; // Must match the order in MyAvatar::AllowAvatarLeaningPreference. + assert(items.size() == static_cast(MyAvatar::AllowAvatarLeaningPreference::Count)); + preference->setHeading("Allow my avatar to lean:"); preference->setItems(items); preferences->addPreference(preference); } diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 06fe558964..bcdef45440 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1855,6 +1855,16 @@ glm::vec3 Rig::deflectHandFromTorso(const glm::vec3& handPosition, const HFMJoin return position; } +// Get the scale factor to convert distances in the geometry frame into the unscaled rig frame. +// Typically it will be the unit conversion from cm to m. +float Rig::GetScaleFactorGeometryToUnscaledRig() const { + // Normally the model offset transform will contain the avatar scale factor; we explicitly remove it here. + AnimPose modelOffsetWithoutAvatarScale(glm::vec3(1.0f), getModelOffsetPose().rot(), getModelOffsetPose().trans()); + AnimPose geomToRigWithoutAvatarScale = modelOffsetWithoutAvatarScale * getGeometryOffsetPose(); + + return geomToRigWithoutAvatarScale.scale().x; // in practice this is always a uniform scale factor. +} + void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnabled, bool hipsEstimated, bool leftArmEnabled, bool rightArmEnabled, bool headEnabled, float dt, const AnimPose& leftHandPose, const AnimPose& rightHandPose, @@ -2703,10 +2713,10 @@ void Rig::computeAvatarBoundingCapsule( Extents totalExtents; totalExtents.reset(); - // HACK by convention our Avatars are always modeled such that y=0 is the ground plane. - // add the zero point so that our avatars will always have bounding volumes that are flush with the ground + // HACK by convention our Avatars are always modeled such that y=0 (GEOMETRY_GROUND_Y) is the ground plane. + // add the ground point so that our avatars will always have bounding volumes that are flush with the ground // even if they do not have legs (default robot) - totalExtents.addPoint(glm::vec3(0.0f)); + totalExtents.addPoint(glm::vec3(0.f, GEOMETRY_GROUND_Y, 0.f)); // To reduce the radius of the bounding capsule to be tight with the torso, we only consider joints // from the head to the hips when computing the rest of the bounding capsule. @@ -2747,24 +2757,20 @@ void Rig::initFlow(bool isActive) { } } +// Get the vertical position of eye joints, in the rig coordinate frame, ignoring the avatar scale. float Rig::getUnscaledEyeHeight() const { // Normally the model offset transform will contain the avatar scale factor, we explicitly remove it here. AnimPose modelOffsetWithoutAvatarScale(glm::vec3(1.0f), getModelOffsetPose().rot(), getModelOffsetPose().trans()); AnimPose geomToRigWithoutAvatarScale = modelOffsetWithoutAvatarScale * getGeometryOffsetPose(); - // This factor can be used to scale distances in the geometry frame into the unscaled rig frame. - // Typically it will be the unit conversion from cm to m. - float scaleFactor = geomToRigWithoutAvatarScale.scale().x; // in practice this always a uniform scale factor. + // Factor to scale distances in the geometry frame into the unscaled rig frame. + float scaleFactor = GetScaleFactorGeometryToUnscaledRig(); int headTopJoint = indexOfJoint("HeadTop_End"); int headJoint = indexOfJoint("Head"); int eyeJoint = indexOfJoint("LeftEye") != -1 ? indexOfJoint("LeftEye") : indexOfJoint("RightEye"); int toeJoint = indexOfJoint("LeftToeBase") != -1 ? indexOfJoint("LeftToeBase") : indexOfJoint("RightToeBase"); - // Makes assumption that the y = 0 plane in geometry is the ground plane. - // We also make that assumption in Rig::computeAvatarBoundingCapsule() - const float GROUND_Y = 0.0f; - // Values from the skeleton are in the geometry coordinate frame. auto skeleton = getAnimSkeleton(); if (eyeJoint >= 0 && toeJoint >= 0) { @@ -2772,8 +2778,8 @@ float Rig::getUnscaledEyeHeight() const { float eyeHeight = skeleton->getAbsoluteDefaultPose(eyeJoint).trans().y - skeleton->getAbsoluteDefaultPose(toeJoint).trans().y; return scaleFactor * eyeHeight; } else if (eyeJoint >= 0) { - // Measure Eye joint to y = 0 plane. - float eyeHeight = skeleton->getAbsoluteDefaultPose(eyeJoint).trans().y - GROUND_Y; + // Measure Eye joint to ground plane. + float eyeHeight = skeleton->getAbsoluteDefaultPose(eyeJoint).trans().y - GEOMETRY_GROUND_Y; return scaleFactor * eyeHeight; } else if (headTopJoint >= 0 && toeJoint >= 0) { // Measure from ToeBase joint to HeadTop_End joint, then remove forehead distance. @@ -2783,19 +2789,36 @@ float Rig::getUnscaledEyeHeight() const { } else if (headTopJoint >= 0) { // Measure from HeadTop_End joint to the ground, then remove forehead distance. const float ratio = DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD / DEFAULT_AVATAR_HEIGHT; - float headHeight = skeleton->getAbsoluteDefaultPose(headTopJoint).trans().y - GROUND_Y; + float headHeight = skeleton->getAbsoluteDefaultPose(headTopJoint).trans().y - GEOMETRY_GROUND_Y; return scaleFactor * (headHeight - headHeight * ratio); } else if (headJoint >= 0) { // Measure Head joint to the ground, then add in distance from neck to eye. const float DEFAULT_AVATAR_NECK_TO_EYE = DEFAULT_AVATAR_NECK_TO_TOP_OF_HEAD - DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD; const float ratio = DEFAULT_AVATAR_NECK_TO_EYE / DEFAULT_AVATAR_NECK_HEIGHT; - float neckHeight = skeleton->getAbsoluteDefaultPose(headJoint).trans().y - GROUND_Y; + float neckHeight = skeleton->getAbsoluteDefaultPose(headJoint).trans().y - GEOMETRY_GROUND_Y; return scaleFactor * (neckHeight + neckHeight * ratio); } else { return DEFAULT_AVATAR_EYE_HEIGHT; } } +// Get the vertical position of the hips joint, in the rig coordinate frame, ignoring the avatar scale. +float Rig::getUnscaledHipsHeight() const { + // This factor can be used to scale distances in the geometry frame into the unscaled rig frame. + const float scaleFactor = GetScaleFactorGeometryToUnscaledRig(); + + const int hipsJoint = indexOfJoint("Hips"); + + // Values from the skeleton are in the geometry coordinate frame. + if (hipsJoint >= 0) { + // Measure hip joint to ground plane. + float hipsHeight = getAnimSkeleton()->getAbsoluteDefaultPose(hipsJoint).trans().y - GEOMETRY_GROUND_Y; + return scaleFactor * hipsHeight; + } else { + return DEFAULT_AVATAR_HIPS_HEIGHT; + } +} + void Rig::setDirectionalBlending(const QString& targetName, const glm::vec3& blendingTarget, const QString& alphaName, float alpha) { _animVars.set(targetName, blendingTarget); _animVars.set(alphaName, alpha); diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index 60a2602316..c58be799cf 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -251,6 +251,7 @@ public: Flow& getFlow() { return _internalFlow; } float getUnscaledEyeHeight() const; + float getUnscaledHipsHeight() const; void buildAbsoluteRigPoses(const AnimPoseVec& relativePoses, AnimPoseVec& absolutePosesOut) const; int getOverrideJointCount() const; @@ -287,6 +288,11 @@ protected: glm::vec3 deflectHandFromTorso(const glm::vec3& handPosition, const HFMJointShapeInfo& hipsShapeInfo, const HFMJointShapeInfo& spineShapeInfo, const HFMJointShapeInfo& spine1ShapeInfo, const HFMJointShapeInfo& spine2ShapeInfo) const; + // Get the scale factor to convert distances in the geometry frame into the unscaled rig frame. + float GetScaleFactorGeometryToUnscaledRig() const; + + // The ground plane Y position in geometry space. + static constexpr float GEOMETRY_GROUND_Y = 0.0f; AnimPose _modelOffset; // model to rig space AnimPose _geometryOffset; // geometry to model space (includes unit offset & fst offsets) diff --git a/libraries/physics/src/CharacterController.cpp b/libraries/physics/src/CharacterController.cpp index e222692aea..d14e59dbd2 100755 --- a/libraries/physics/src/CharacterController.cpp +++ b/libraries/physics/src/CharacterController.cpp @@ -107,12 +107,12 @@ CharacterController::CharacterMotor::CharacterMotor(const glm::vec3& vel, const static uint32_t _numCharacterControllers { 0 }; -CharacterController::CharacterController() { +CharacterController::CharacterController(const FollowTimePerType& followTimeRemainingPerType) : + _followTimeRemainingPerType(followTimeRemainingPerType) { _floorDistance = _scaleFactor * DEFAULT_AVATAR_FALL_HEIGHT; _targetVelocity.setValue(0.0f, 0.0f, 0.0f); _followDesiredBodyTransform.setIdentity(); - _followTimeRemaining = 0.0f; _state = State::Hover; _isPushingUp = false; _rayHitStartTime = 0; @@ -351,66 +351,108 @@ void CharacterController::playerStep(btCollisionWorld* collisionWorld, btScalar computeNewVelocity(dt, velocity); const float MINIMUM_TIME_REMAINING = 0.005f; - const float MAX_DISPLACEMENT = 0.5f * _radius; - _followTimeRemaining -= dt; - if (_followTimeRemaining >= MINIMUM_TIME_REMAINING) { - btTransform bodyTransform = _rigidBody->getWorldTransform(); + static_assert(FOLLOW_TIME_IMMEDIATE_SNAP > MINIMUM_TIME_REMAINING, "The code below assumes this condition is true."); + bool hasFollowTimeRemaining = false; + for (float followTime : _followTimeRemainingPerType) { + if (followTime > MINIMUM_TIME_REMAINING) { + hasFollowTimeRemaining = true; + break; + } + } + + if (hasFollowTimeRemaining) { + const float MAX_DISPLACEMENT = 0.5f * _radius; + + btTransform bodyTransform = _rigidBody->getWorldTransform(); btVector3 startPos = bodyTransform.getOrigin(); btVector3 deltaPos = _followDesiredBodyTransform.getOrigin() - startPos; - btVector3 vel = deltaPos / _followTimeRemaining; - btVector3 linearDisplacement = clampLength(vel * dt, MAX_DISPLACEMENT); // clamp displacement to prevent tunneling. + + btVector3 linearDisplacement(0, 0, 0); + { + linearDisplacement.setZero(); + + const float horizontalTime = _followTimeRemainingPerType[static_cast(FollowType::Horizontal)]; + const float verticalTime = _followTimeRemainingPerType[static_cast(FollowType::Vertical)]; + + if (horizontalTime == FOLLOW_TIME_IMMEDIATE_SNAP) { + linearDisplacement.setX(deltaPos.x()); + linearDisplacement.setZ(deltaPos.z()); + } else if (horizontalTime > MINIMUM_TIME_REMAINING) { + linearDisplacement.setX((deltaPos.x() * dt) / horizontalTime); + linearDisplacement.setZ((deltaPos.z() * dt) / horizontalTime); + } + + if (verticalTime == FOLLOW_TIME_IMMEDIATE_SNAP) { + linearDisplacement.setY(deltaPos.y()); + } else if (verticalTime > MINIMUM_TIME_REMAINING) { + linearDisplacement.setY((deltaPos.y() * dt) / verticalTime); + } + + linearDisplacement = clampLength(linearDisplacement, MAX_DISPLACEMENT); // clamp displacement to prevent tunneling. + } + btVector3 endPos = startPos + linearDisplacement; // resolve the simple linearDisplacement _followLinearDisplacement += linearDisplacement; // now for the rotational part... + btQuaternion startRot = bodyTransform.getRotation(); - btQuaternion desiredRot = _followDesiredBodyTransform.getRotation(); // startRot as default rotation btQuaternion endRot = startRot; - // the dot product between two quaternions is equal to +/- cos(angle/2) - // where 'angle' is that of the rotation between them - float qDot = desiredRot.dot(startRot); + const float rotationTime = _followTimeRemainingPerType[static_cast(FollowType::Rotation)]; + if (rotationTime > MINIMUM_TIME_REMAINING) { + btQuaternion desiredRot = _followDesiredBodyTransform.getRotation(); - // when the abs() value of the dot product is approximately 1.0 - // then the two rotations are effectively adjacent - const float MIN_DOT_PRODUCT_OF_ADJACENT_QUATERNIONS = 0.99999f; // corresponds to approx 0.5 degrees - if (fabsf(qDot) < MIN_DOT_PRODUCT_OF_ADJACENT_QUATERNIONS) { - if (qDot < 0.0f) { - // the quaternions are actually on opposite hyperhemispheres - // so we move one to agree with the other and negate qDot - desiredRot = -desiredRot; - qDot = -qDot; + // the dot product between two quaternions is equal to +/- cos(angle/2) + // where 'angle' is that of the rotation between them + float qDot = desiredRot.dot(startRot); + + // when the abs() value of the dot product is approximately 1.0 + // then the two rotations are effectively adjacent + const float MIN_DOT_PRODUCT_OF_ADJACENT_QUATERNIONS = 0.99999f; // corresponds to approx 0.5 degrees + if (fabsf(qDot) < MIN_DOT_PRODUCT_OF_ADJACENT_QUATERNIONS) { + if (qDot < 0.0f) { + // the quaternions are actually on opposite hyperhemispheres + // so we move one to agree with the other and negate qDot + desiredRot = -desiredRot; + qDot = -qDot; + } + btQuaternion deltaRot = desiredRot * startRot.inverse(); + + // the axis is the imaginary part, but scaled by sin(angle/2) + btVector3 axis(deltaRot.getX(), deltaRot.getY(), deltaRot.getZ()); + axis /= sqrtf(1.0f - qDot * qDot); + + // compute the angle we will resolve for this dt, but don't overshoot + float angle = 2.0f * acosf(qDot); + + if (rotationTime != FOLLOW_TIME_IMMEDIATE_SNAP) { + if (dt < rotationTime) { + angle *= dt / rotationTime; + } + } + + // accumulate rotation + deltaRot = btQuaternion(axis, angle); + _followAngularDisplacement = (deltaRot * _followAngularDisplacement).normalize(); + + // in order to accumulate displacement of avatar position, we need to take _shapeLocalOffset into account. + btVector3 shapeLocalOffset = glmToBullet(_shapeLocalOffset); + + endRot = deltaRot * startRot; + btVector3 swingDisplacement = + rotateVector(endRot, -shapeLocalOffset) - rotateVector(startRot, -shapeLocalOffset); + _followLinearDisplacement += swingDisplacement; } - btQuaternion deltaRot = desiredRot * startRot.inverse(); - - // the axis is the imaginary part, but scaled by sin(angle/2) - btVector3 axis(deltaRot.getX(), deltaRot.getY(), deltaRot.getZ()); - axis /= sqrtf(1.0f - qDot * qDot); - - // compute the angle we will resolve for this dt, but don't overshoot - float angle = 2.0f * acosf(qDot); - if (dt < _followTimeRemaining) { - angle *= dt / _followTimeRemaining; - } - - // accumulate rotation - deltaRot = btQuaternion(axis, angle); - _followAngularDisplacement = (deltaRot * _followAngularDisplacement).normalize(); - - // in order to accumulate displacement of avatar position, we need to take _shapeLocalOffset into account. - btVector3 shapeLocalOffset = glmToBullet(_shapeLocalOffset); - - endRot = deltaRot * startRot; - btVector3 swingDisplacement = rotateVector(endRot, -shapeLocalOffset) - rotateVector(startRot, -shapeLocalOffset); - _followLinearDisplacement += swingDisplacement; } _rigidBody->setWorldTransform(btTransform(endRot, endPos)); } + _followTime += dt; if (_steppingUp) { @@ -606,8 +648,7 @@ void CharacterController::setParentVelocity(const glm::vec3& velocity) { _parentVelocity = glmToBullet(velocity); } -void CharacterController::setFollowParameters(const glm::mat4& desiredWorldBodyMatrix, float timeRemaining) { - _followTimeRemaining = timeRemaining; +void CharacterController::setFollowParameters(const glm::mat4& desiredWorldBodyMatrix) { _followDesiredBodyTransform = glmToBullet(desiredWorldBodyMatrix) * btTransform(btQuaternion::getIdentity(), glmToBullet(_shapeLocalOffset)); } diff --git a/libraries/physics/src/CharacterController.h b/libraries/physics/src/CharacterController.h index e7ad3ddfa8..899c99890b 100755 --- a/libraries/physics/src/CharacterController.h +++ b/libraries/physics/src/CharacterController.h @@ -53,7 +53,21 @@ const btScalar MIN_CHARACTER_MOTOR_TIMESCALE = 0.05f; class CharacterController : public btCharacterControllerInterface { public: - CharacterController(); + enum class FollowType : uint8_t + { + Rotation, + Horizontal, + Vertical, + Count + }; + + // Remaining follow time for each FollowType + typedef std::array(FollowType::Count)> FollowTimePerType; + + // Follow time value meaning that we should snap immediately to the target. + static constexpr float FOLLOW_TIME_IMMEDIATE_SNAP = FLT_MAX; + + CharacterController(const FollowTimePerType& followTimeRemainingPerType); virtual ~CharacterController(); bool needsRemoval() const; bool needsAddition() const; @@ -99,7 +113,8 @@ public: void getPositionAndOrientation(glm::vec3& position, glm::quat& rotation) const; void setParentVelocity(const glm::vec3& parentVelocity); - void setFollowParameters(const glm::mat4& desiredWorldMatrix, float timeRemaining); + + void setFollowParameters(const glm::mat4& desiredWorldMatrix); float getFollowTime() const { return _followTime; } glm::vec3 getFollowLinearDisplacement() const; glm::quat getFollowAngularDisplacement() const; @@ -144,7 +159,7 @@ public: void setPendingFlagsUpdateCollisionMask(){ _pendingFlags |= PENDING_FLAG_UPDATE_COLLISION_MASK; } void setSeated(bool isSeated) { _isSeated = isSeated; } - bool getSeated() { return _isSeated; } + bool getSeated() const { return _isSeated; } void resetStuckCounter() { _numStuckSubsteps = 0; } @@ -178,7 +193,7 @@ protected: btVector3 _preSimulationVelocity; btVector3 _velocityChange; btTransform _followDesiredBodyTransform; - btScalar _followTimeRemaining; + const FollowTimePerType& _followTimeRemainingPerType; btTransform _characterBodyTransform; btVector3 _position; btQuaternion _rotation; diff --git a/libraries/shared/src/AvatarConstants.h b/libraries/shared/src/AvatarConstants.h index 4a79f6b487..a25142b2ad 100644 --- a/libraries/shared/src/AvatarConstants.h +++ b/libraries/shared/src/AvatarConstants.h @@ -20,6 +20,7 @@ const float DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD = 0.11f; // meters const float DEFAULT_AVATAR_NECK_TO_TOP_OF_HEAD = 0.185f; // meters const float DEFAULT_AVATAR_NECK_HEIGHT = DEFAULT_AVATAR_HEIGHT - DEFAULT_AVATAR_NECK_TO_TOP_OF_HEAD; const float DEFAULT_AVATAR_EYE_HEIGHT = DEFAULT_AVATAR_HEIGHT - DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD; +const float DEFAULT_AVATAR_HIPS_HEIGHT = 1.01327407f; // meters const float DEFAULT_SPINE2_SPLINE_PROPORTION = 0.71f; const float DEFAULT_AVATAR_SUPPORT_BASE_LEFT = -0.25f; const float DEFAULT_AVATAR_SUPPORT_BASE_RIGHT = 0.25f; From 6fc40385ca8270193f57aa671eedc50738228d01 Mon Sep 17 00:00:00 2001 From: Phil Palmer Date: Tue, 22 Dec 2020 15:51:03 -0500 Subject: [PATCH 02/28] Fix compile error building macOS-latest: changed MINIMUM_TIME_REMAINING from const to constexpr in CharacterController::playerStep. Error was: "static_assert expression is not an integral constant expression", "read of non-constexpr variable 'MINIMUM_TIME_REMAINING' is not allowed in a constant expression". Error started in last commit (2179c153de8bd8d1b455894e9ccd3699df133c3f). --- libraries/physics/src/CharacterController.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/physics/src/CharacterController.cpp b/libraries/physics/src/CharacterController.cpp index d14e59dbd2..aa4d8c42e3 100755 --- a/libraries/physics/src/CharacterController.cpp +++ b/libraries/physics/src/CharacterController.cpp @@ -350,7 +350,7 @@ void CharacterController::playerStep(btCollisionWorld* collisionWorld, btScalar btVector3 velocity = _rigidBody->getLinearVelocity() - _parentVelocity; computeNewVelocity(dt, velocity); - const float MINIMUM_TIME_REMAINING = 0.005f; + constexpr float MINIMUM_TIME_REMAINING = 0.005f; static_assert(FOLLOW_TIME_IMMEDIATE_SNAP > MINIMUM_TIME_REMAINING, "The code below assumes this condition is true."); bool hasFollowTimeRemaining = false; From 287f710ea138a6fc6ce4e8630d3de7e3e9dc35fd Mon Sep 17 00:00:00 2001 From: Phil Palmer Date: Tue, 22 Dec 2020 19:32:43 -0500 Subject: [PATCH 03/28] Fix MyAvatar::centerBody breaking existing scripts (eg. away.js) by having a new parameter: Moved the body of the function to a private internal method (centerBodyInternal), which takes the parameter instead. Previously, when leaving 'away' state, the 'Away' overlay would stay on screen because of the bug. The bug started in "VR fixes for: couldn't sit on the floor, wrong walk directions." (2179c153de8bd8d1b455894e9ccd3699df133c3f). --- interface/src/avatar/MyAvatar.cpp | 16 ++++++++++------ interface/src/avatar/MyAvatar.h | 3 ++- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index e5413105f8..be5fa39e1b 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -497,14 +497,18 @@ void MyAvatar::resetSensorsAndBody() { reset(true, false, true); } -// forceFollowYPos: true to force the body matrix to be affected by the HMD's -// vertical position, even if crouch recentering is disabled. -void MyAvatar::centerBody(const bool forceFollowYPos) { +void MyAvatar::centerBody() { if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "centerBody"); return; } + centerBodyInternal(false); +} + +// forceFollowYPos (default false): true to force the body matrix to be affected by the HMD's +// vertical position, even if crouch recentering is disabled. +void MyAvatar::centerBodyInternal(const bool forceFollowYPos) { // derive the desired body orientation from the current hmd orientation, before the sensor reset. auto newBodySensorMatrix = deriveBodyFromHMDSensor(forceFollowYPos); // Based on current cached HMD position/rotation.. @@ -5323,7 +5327,7 @@ void MyAvatar::setAllowAvatarStandingPreference(const MyAvatar::AllowAvatarStand // Set the correct vertical position for the avatar body relative to the HMD, // according to the newly-selected avatar standing preference. - centerBody(false); + centerBodyInternal(false); } // Set the user preference of when the avatar may lean. @@ -6693,7 +6697,7 @@ void MyAvatar::beginSit(const glm::vec3& position, const glm::quat& rotation) { setHMDLeanRecenterEnabled(false); // Disable movement setSitDriveKeysStatus(false); - centerBody(true); + centerBodyInternal(true); int hipIndex = getJointIndex("Hips"); clearPinOnJoint(hipIndex); pinJoint(hipIndex, position, rotation); @@ -6711,7 +6715,7 @@ void MyAvatar::endSit(const glm::vec3& position, const glm::quat& rotation) { _characterController.setSeated(false); setCollisionsEnabled(true); setHMDLeanRecenterEnabled(true); - centerBody(false); + centerBodyInternal(false); slamPosition(position); setWorldOrientation(rotation); diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 9e817b5167..c06a1a88ab 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -594,7 +594,7 @@ public: * the HMD. * @function MyAvatar.centerBody */ - Q_INVOKABLE void centerBody(const bool forceFollowYPos); // thread-safe + Q_INVOKABLE void centerBody(); // thread-safe /**jsdoc @@ -2812,6 +2812,7 @@ private: void resetLookAtRotation(const glm::vec3& avatarPosition, const glm::quat& avatarOrientation); void resetPointAt(); static glm::vec3 aimToBlendValues(const glm::vec3& aimVector, const glm::quat& frameOrientation); + void centerBodyInternal(const bool forceFollowYPos = false); // Avatar Preferences QUrl _fullAvatarURLFromPreferences; From 8b839fe71b9a2d412100d61671fbd1d71cb6d8c9 Mon Sep 17 00:00:00 2001 From: Phil Palmer Date: Wed, 23 Dec 2020 07:54:59 -0500 Subject: [PATCH 04/28] VR fixes for different user heights and avatar scales. - The user's real-world height is now taken into account in MyAvatar::deriveBodyFromHMDSensor. Therefore, for any user height, the floor stays correctly positioned in all modes of 'Allow my avatar to stand'. - Whenever the user's real-world height is changed, centerBodyInternal is now called to position the body accordingly. The floor therefore stays correctly positioned in all modes of 'Allow my avatar to stand'. (MyAvatar::setUserHeight) - Fix for walk speeds in VR being too fast at large avatar scale and too slow at small avatar scale. The action motor velocity was being scaled once too many by the sensor-to-world scale. The bug existed before this branch. (MyAvatar::scaleMotorSpeed) --- interface/src/avatar/MyAvatar.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index be5fa39e1b..d609ca0efc 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -3758,15 +3758,15 @@ glm::vec3 MyAvatar::scaleMotorSpeed(const glm::vec3 forward, const glm::vec3 rig if (length > EPSILON) { direction /= length; } - return getSensorToWorldScale() * direction * getSprintSpeed() * _walkSpeedScalar; + return direction * getSprintSpeed() * _walkSpeedScalar; } else { return Vectors::ZERO; } case LocomotionControlsMode::CONTROLS_ANALOG: case LocomotionControlsMode::CONTROLS_ANALOG_PLUS: if (zSpeed || xSpeed) { - glm::vec3 scaledForward = getSensorToWorldScale() * calculateGearedSpeed(zSpeed) * _walkSpeedScalar * ((zSpeed >= stickFullOn) ? getSprintSpeed() : getWalkSpeed()) * forward; - glm::vec3 scaledRight = getSensorToWorldScale() * calculateGearedSpeed(xSpeed) * _walkSpeedScalar * ((xSpeed > stickFullOn) ? getSprintSpeed() : getWalkSpeed()) * right; + glm::vec3 scaledForward = calculateGearedSpeed(zSpeed) * _walkSpeedScalar * ((zSpeed >= stickFullOn) ? getSprintSpeed() : getWalkSpeed()) * forward; + glm::vec3 scaledRight = calculateGearedSpeed(xSpeed) * _walkSpeedScalar * ((xSpeed > stickFullOn) ? getSprintSpeed() : getWalkSpeed()) * right; direction = scaledForward + scaledRight; return direction; } else { @@ -4785,6 +4785,7 @@ void MyAvatar::triggerRotationRecenter() { _follow.setForceActivateRotation(true); } +// Derive the sensor-space matrix for the body, based on the pose of the HMD and hips tracker. // old school meat hook style // forceFollowYPos (default false): true to force the body matrix to be affected by the HMD's // vertical position, even if crouch recentering is disabled. @@ -4822,8 +4823,8 @@ glm::mat4 MyAvatar::deriveBodyFromHMDSensor(const bool forceFollowYPos) const { glm::vec3 headToNeck = headOrientation * Quaternions::Y_180 * (localNeck - localHead); glm::vec3 neckToRoot = headOrientationYawOnly * Quaternions::Y_180 * -localNeck; - float invSensorToWorldScale = getUserEyeHeight() / getEyeHeight(); - glm::vec3 bodyPos = headPosition + invSensorToWorldScale * (headToNeck + neckToRoot); + const float worldToSensorScale = getUserEyeHeight() / getEyeHeight(); + glm::vec3 bodyPos = headPosition + worldToSensorScale * (headToNeck + neckToRoot); glm::quat bodyQuat; const controller::Pose hipsControllerPose = getControllerPoseInSensorFrame(controller::Action::HIPS); @@ -4842,7 +4843,7 @@ glm::mat4 MyAvatar::deriveBodyFromHMDSensor(const bool forceFollowYPos) const { if (!forceFollowYPos && !getHMDCrouchRecenterEnabled()) { // Set the body's vertical position as if it were standing in its T-pose. - bodyPos.y = rig.getUnscaledHipsHeight(); + bodyPos.y = worldToSensorScale * rig.getUnscaledHipsHeight(); } glm::mat4 bodyMat = createMatFromQuatAndPos(bodyQuat, bodyPos); @@ -5230,6 +5231,7 @@ float MyAvatar::getUserHeight() const { void MyAvatar::setUserHeight(float value) { _userHeight.set(value); + centerBodyInternal(false); float sensorToWorldScale = getEyeHeight() / getUserEyeHeight(); emit sensorToWorldScaleChanged(sensorToWorldScale); From a489e9ddcae0b391c485ac9dee10d9fb638d7003 Mon Sep 17 00:00:00 2001 From: Phil Palmer Date: Wed, 23 Dec 2020 19:00:05 -0500 Subject: [PATCH 05/28] Code style: made some little things more conformant with the coding standards and the rest of the codebase. https://github.com/vircadia/vircadia/blob/master/CODING_STANDARD.md --- interface/src/avatar/MyAvatar.cpp | 22 +++++++++---------- libraries/animation/src/Rig.cpp | 2 +- libraries/physics/src/CharacterController.cpp | 5 +---- 3 files changed, 13 insertions(+), 16 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index d609ca0efc..e4189b8bf2 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -5562,8 +5562,8 @@ MyAvatar::FollowHelper::FollowHelper() { } void MyAvatar::FollowHelper::deactivate() { - for (uint i = 0, e = static_cast(CharacterController::FollowType::Count); i < e; ++i) { - deactivate((CharacterController::FollowType)i); + for (uint i = 0; i < static_cast(CharacterController::FollowType::Count); i++) { + deactivate(static_cast(i)); } } @@ -5588,8 +5588,8 @@ bool MyAvatar::FollowHelper::isActive(CharacterController::FollowType type) cons } bool MyAvatar::FollowHelper::isActive() const { - for (uint i = 0, e = static_cast(CharacterController::FollowType::Count); i < e; ++i) { - if (isActive((CharacterController::FollowType)i)) { + for (uint i = 0; i < static_cast(CharacterController::FollowType::Count); i++) { + if (isActive(static_cast(i))) { return true; } } @@ -5597,12 +5597,12 @@ bool MyAvatar::FollowHelper::isActive() const { } void MyAvatar::FollowHelper::decrementTimeRemaining(float dt) { - for (uint i = 0, e = static_cast(CharacterController::FollowType::Count); i < e; ++i) { - if (_timeRemaining[i] == CharacterController::FOLLOW_TIME_IMMEDIATE_SNAP) { - _timeRemaining[i] = 0.f; + for (auto& time : _timeRemaining) { + if (time == CharacterController::FOLLOW_TIME_IMMEDIATE_SNAP) { + time = 0.0f; } else { - _timeRemaining[i] -= dt; + time -= dt; } } } @@ -5612,13 +5612,14 @@ bool MyAvatar::FollowHelper::shouldActivateRotation(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix, bool& shouldSnapOut) const { - shouldSnapOut = false; - // If hips are under direct control (tracked), they give our desired body rotation and we snap to it every frame. if (myAvatar.areHipsTracked()) { shouldSnapOut = true; return true; } + else { + shouldSnapOut = false; + } const float FOLLOW_ROTATION_THRESHOLD = cosf(myAvatar.getRotationThreshold()); glm::vec2 bodyFacing = getFacingDir2D(currentBodyMatrix); @@ -5875,7 +5876,6 @@ glm::mat4 MyAvatar::FollowHelper::postPhysicsUpdate(MyAvatar& myAvatar, const gl setTranslation(newBodyMat, extractTranslation(myAvatar.deriveBodyFromHMDSensor())); } } - return newBodyMat; } else { return currentBodyMatrix; diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index bcdef45440..8b49334925 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -2716,7 +2716,7 @@ void Rig::computeAvatarBoundingCapsule( // HACK by convention our Avatars are always modeled such that y=0 (GEOMETRY_GROUND_Y) is the ground plane. // add the ground point so that our avatars will always have bounding volumes that are flush with the ground // even if they do not have legs (default robot) - totalExtents.addPoint(glm::vec3(0.f, GEOMETRY_GROUND_Y, 0.f)); + totalExtents.addPoint(glm::vec3(0.0f, GEOMETRY_GROUND_Y, 0.0f)); // To reduce the radius of the bounding capsule to be tight with the torso, we only consider joints // from the head to the hips when computing the rest of the bounding capsule. diff --git a/libraries/physics/src/CharacterController.cpp b/libraries/physics/src/CharacterController.cpp index aa4d8c42e3..04ccd9e547 100755 --- a/libraries/physics/src/CharacterController.cpp +++ b/libraries/physics/src/CharacterController.cpp @@ -368,10 +368,8 @@ void CharacterController::playerStep(btCollisionWorld* collisionWorld, btScalar btVector3 startPos = bodyTransform.getOrigin(); btVector3 deltaPos = _followDesiredBodyTransform.getOrigin() - startPos; - btVector3 linearDisplacement(0, 0, 0); + btVector3 linearDisplacement(0.0f, 0.0f, 0.0f); { - linearDisplacement.setZero(); - const float horizontalTime = _followTimeRemainingPerType[static_cast(FollowType::Horizontal)]; const float verticalTime = _followTimeRemainingPerType[static_cast(FollowType::Vertical)]; @@ -452,7 +450,6 @@ void CharacterController::playerStep(btCollisionWorld* collisionWorld, btScalar } _rigidBody->setWorldTransform(btTransform(endRot, endPos)); } - _followTime += dt; if (_steppingUp) { From 20e4f952ab4c08b59ea589ea9bb3d5cb054d7f04 Mon Sep 17 00:00:00 2001 From: Phil Palmer Date: Sat, 26 Dec 2020 23:19:21 -0500 Subject: [PATCH 06/28] added pragmas --- interface/src/avatar/MyAvatar.cpp | 2 ++ interface/src/avatar/MySkeletonModel.cpp | 2 +- interface/src/ui/PreferencesDialog.cpp | 2 ++ libraries/animation/src/Rig.cpp | 2 ++ libraries/physics/src/CharacterController.cpp | 2 ++ 5 files changed, 9 insertions(+), 1 deletion(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index b3231b906d..76058d35d9 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -68,6 +68,8 @@ #include "MovingEntitiesOperator.h" #include "SceneScriptingInterface.h" +#pragma optimize("", off) + using namespace std; const float DEFAULT_REAL_WORLD_FIELD_OF_VIEW_DEGREES = 30.0f; diff --git a/interface/src/avatar/MySkeletonModel.cpp b/interface/src/avatar/MySkeletonModel.cpp index 6fe199aaba..6e339e2d37 100755 --- a/interface/src/avatar/MySkeletonModel.cpp +++ b/interface/src/avatar/MySkeletonModel.cpp @@ -16,7 +16,7 @@ #include "InterfaceLogging.h" #include "AnimUtil.h" - +#pragma optimize("", off) MySkeletonModel::MySkeletonModel(Avatar* owningAvatar, QObject* parent) : SkeletonModel(owningAvatar, parent) { } diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index 79d9ebaa5c..34317ca11e 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -26,6 +26,8 @@ #include "UserActivityLogger.h" #include "ui/Keyboard.h" +#pragma optimize("", off) + void setupPreferences() { auto preferences = DependencyManager::get(); auto myAvatar = DependencyManager::get()->getMyAvatar(); diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 06fe558964..6ef2a3aae6 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -35,6 +35,8 @@ #include "IKTarget.h" #include "PathUtils.h" +#pragma optimize("", off) + static int nextRigId = 1; static std::map rigRegistry; static std::mutex rigRegistryMutex; diff --git a/libraries/physics/src/CharacterController.cpp b/libraries/physics/src/CharacterController.cpp index e222692aea..ba59e5d781 100755 --- a/libraries/physics/src/CharacterController.cpp +++ b/libraries/physics/src/CharacterController.cpp @@ -20,6 +20,8 @@ #include "PhysicsLogging.h" #include "TemporaryPairwiseCollisionFilter.h" +#pragma optimize("", off) + const float STUCK_PENETRATION = -0.05f; // always negative into the object. const float STUCK_IMPULSE = 500.0f; From dec9e9d3380c4c03e4997d254170a16273f5d7fb Mon Sep 17 00:00:00 2001 From: Phil Palmer Date: Sun, 27 Dec 2020 02:50:29 -0500 Subject: [PATCH 07/28] VR fix for different avatar scales. Previously, if the avatar scale wasn't 1, the body and viewpoint would given the wrong vertical position in some situations, eg: getting up from click-to-sit; changing the 'Allow my avatar to stand' setting. The avatar scale was being taken into account where it shouldn't have in MyAvatar::deriveBodyFromHMDSensor. The bug started in 8b839fe71b9a2d412100d61671fbd1d71cb6d8c9 ("VR fixes for different user heights and avatar scales". Repro: 1. Set 'Allow my avatar to stand: Always' 2. Reduce the avatar scale to the minumum. 3. Set 'Allow my avatar to stand: When I stand' 4. Previously, the viewpoint would suddenly be below the floor. --- interface/src/avatar/MyAvatar.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 4694df155e..72d6190f66 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -4845,7 +4845,8 @@ glm::mat4 MyAvatar::deriveBodyFromHMDSensor(const bool forceFollowYPos) const { if (!forceFollowYPos && !getHMDCrouchRecenterEnabled()) { // Set the body's vertical position as if it were standing in its T-pose. - bodyPos.y = worldToSensorScale * rig.getUnscaledHipsHeight(); + const float userToRigScale = getUserEyeHeight() / getUnscaledEyeHeight(); + bodyPos.y = userToRigScale * rig.getUnscaledHipsHeight(); } glm::mat4 bodyMat = createMatFromQuatAndPos(bodyQuat, bodyPos); From 3e25e32f18bc6450c2933f98ff9a4c6ffe6c3bbb Mon Sep 17 00:00:00 2001 From: Phil Palmer Date: Sun, 27 Dec 2020 02:52:52 -0500 Subject: [PATCH 08/28] Revert temporary "added pragmas" (optimize off) This reverts commit 20e4f952ab4c08b59ea589ea9bb3d5cb054d7f04. --- interface/src/avatar/MyAvatar.cpp | 2 -- interface/src/avatar/MySkeletonModel.cpp | 2 +- interface/src/ui/PreferencesDialog.cpp | 2 -- libraries/animation/src/Rig.cpp | 2 -- libraries/physics/src/CharacterController.cpp | 2 -- 5 files changed, 1 insertion(+), 9 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 72d6190f66..4584f25e63 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -68,8 +68,6 @@ #include "MovingEntitiesOperator.h" #include "SceneScriptingInterface.h" -#pragma optimize("", off) - using namespace std; const float DEFAULT_REAL_WORLD_FIELD_OF_VIEW_DEGREES = 30.0f; diff --git a/interface/src/avatar/MySkeletonModel.cpp b/interface/src/avatar/MySkeletonModel.cpp index 06aaf1f85c..2d33ee35ca 100755 --- a/interface/src/avatar/MySkeletonModel.cpp +++ b/interface/src/avatar/MySkeletonModel.cpp @@ -16,7 +16,7 @@ #include "InterfaceLogging.h" #include "AnimUtil.h" -#pragma optimize("", off) + MySkeletonModel::MySkeletonModel(Avatar* owningAvatar, QObject* parent) : SkeletonModel(owningAvatar, parent) { } diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index 817eb2500f..12615426f1 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -26,8 +26,6 @@ #include "UserActivityLogger.h" #include "ui/Keyboard.h" -#pragma optimize("", off) - void setupPreferences() { auto preferences = DependencyManager::get(); auto myAvatar = DependencyManager::get()->getMyAvatar(); diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index b5bda4f0db..8b49334925 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -35,8 +35,6 @@ #include "IKTarget.h" #include "PathUtils.h" -#pragma optimize("", off) - static int nextRigId = 1; static std::map rigRegistry; static std::mutex rigRegistryMutex; diff --git a/libraries/physics/src/CharacterController.cpp b/libraries/physics/src/CharacterController.cpp index 1251dd4512..04ccd9e547 100755 --- a/libraries/physics/src/CharacterController.cpp +++ b/libraries/physics/src/CharacterController.cpp @@ -20,8 +20,6 @@ #include "PhysicsLogging.h" #include "TemporaryPairwiseCollisionFilter.h" -#pragma optimize("", off) - const float STUCK_PENETRATION = -0.05f; // always negative into the object. const float STUCK_IMPULSE = 500.0f; From 2be87fe7dcf47ff33560349fd36d2be6012cf30b Mon Sep 17 00:00:00 2001 From: Phil Palmer Date: Sun, 27 Dec 2020 22:13:09 -0500 Subject: [PATCH 09/28] Fix VR recentering cases that gave incorrect vertical position. Fix for MyAvatar sometimes hovering off the ground after foot tracking was enabled. MyAvatar's body is now recentered when foot-tracking starts or ends (in MyAvatar::update). Repro: - Start without foot tracking, 'Allow my avatar to stand: Always'. - Crouch, and wait a few seconds for the avatar to pop back to the standing position. - Slowly stand straight so that the avatar raises off the ground without recentering (PS: is this a bug?). If it recenters, retry from previous step. - Calibrate foot tracking. Previously the avatar would remain hovering off the ground until MyAvatar::centerBody was called. Fix for MyAvatar popping to a standing position if the left foot lost tracking. The recentering now takes into account if either of the feet are tracked. Repro: - Enable foot tracking (and ideally hips tracking). - Sit on the floor. - Cover the left foot sensor so it loses tracking. Previously the avatar and viewpoint would pop to a standing position. Optimisation: MyAvatar::update now stores bools indicating which body parts are tracked (_isBodyPartTracked). This avoid unnecessary computations that came from using getControllerPoseInAvatarFrame to convert controller::Pose from sensor space to world space and then to avatar space, only to check if the pose was valid. --- interface/src/avatar/MyAvatar.cpp | 53 +++++++++++++++++++------------ interface/src/avatar/MyAvatar.h | 13 ++++++-- 2 files changed, 43 insertions(+), 23 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 4584f25e63..86c6bcd0d8 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -595,7 +595,7 @@ void MyAvatar::updateSitStandState(float newHeightReading, float dt) { const float SITTING_TIMEOUT = 4.0f; // 4 seconds const float STANDING_TIMEOUT = 0.3333f; // 1/3 second const float SITTING_UPPER_BOUND = 1.52f; - if (!getIsAway() && getControllerPoseInAvatarFrame(controller::Action::HEAD).isValid()) { + if (!getIsAway() && _isBodyPartTracked._head) { if (getIsInSittingState()) { if (newHeightReading > (STANDING_HEIGHT_MULTIPLE * _tippingPoint)) { // if we recenter upwards then no longer in sitting state @@ -659,10 +659,35 @@ void MyAvatar::update(float deltaTime) { float tau = deltaTime / HMD_FACING_TIMESCALE; setHipToHandController(computeHandAzimuth()); + // Determine which body parts are under direct control (tracked). + { + _isBodyPartTracked._leftHand = getControllerPoseInSensorFrame(controller::Action::LEFT_HAND).isValid(); + _isBodyPartTracked._rightHand = getControllerPoseInSensorFrame(controller::Action::RIGHT_HAND).isValid(); + _isBodyPartTracked._head = getControllerPoseInSensorFrame(controller::Action::HEAD).isValid(); + + // Check for either foot so that if one foot loses tracking, we don't break out of foot-tracking behaviour + // (in terms of avatar recentering for example). + _isBodyPartTracked._feet = _isBodyPartTracked._head && // Feet can't be tracked unless head is tracked. + (getControllerPoseInSensorFrame(controller::Action::LEFT_FOOT).isValid() || + getControllerPoseInSensorFrame(controller::Action::RIGHT_FOOT).isValid()); + + _isBodyPartTracked._hips = _isBodyPartTracked._feet && // Hips can't be tracked unless feet are tracked. + getControllerPoseInSensorFrame(controller::Action::HIPS).isValid(); + } + + // Recenter the body when foot tracking starts or ends. + { + static bool prevFeetWereTracked = _isBodyPartTracked._feet; + if (_isBodyPartTracked._feet != prevFeetWereTracked) { + centerBodyInternal(false); + prevFeetWereTracked = _isBodyPartTracked._feet; + } + } + // put the average hand azimuth into sensor space. // then mix it with head facing direction to determine rotation recenter int spine2Index = _skeletonModel->getRig().indexOfJoint("Spine2"); - if (getControllerPoseInAvatarFrame(controller::Action::LEFT_HAND).isValid() && getControllerPoseInAvatarFrame(controller::Action::RIGHT_HAND).isValid() && !(spine2Index < 0)) { + if (_isBodyPartTracked._leftHand && _isBodyPartTracked._rightHand && !(spine2Index < 0)) { // use the spine for the azimuth origin. glm::quat spine2Rot = getAbsoluteJointRotationInObjectFrame(spine2Index); @@ -2760,8 +2785,7 @@ void MyAvatar::prepareForPhysicsSimulation() { _characterController.setScaleFactor(getSensorToWorldScale()); _characterController.setPositionAndOrientation(getWorldPosition(), getWorldOrientation()); - auto headPose = getControllerPoseInAvatarFrame(controller::Action::HEAD); - if (headPose.isValid()) { + if (_isBodyPartTracked._head) { _follow.prePhysicsUpdate(*this, deriveBodyFromHMDSensor(), _bodySensorMatrix, hasDriveInput()); } else { _follow.deactivate(); @@ -5146,7 +5170,7 @@ float MyAvatar::computeStandingHeightMode(const controller::Pose& head) { modeInMeters = ((float)mode) / CENTIMETERS_PER_METER; if (!(modeInMeters > getCurrentStandingHeight())) { // if not greater check for a reset - if (getResetMode() && getControllerPoseInAvatarFrame(controller::Action::HEAD).isValid()) { + if (getResetMode() && _isBodyPartTracked._head) { setResetMode(false); float resetModeInCentimeters = glm::floor((head.getTranslation().y - MODE_CORRECTION_FACTOR)*CENTIMETERS_PER_METER); modeInMeters = (resetModeInCentimeters / CENTIMETERS_PER_METER); @@ -5746,8 +5770,6 @@ void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix, bool hasDriveInput) { - const bool feetAreTracked = myAvatar.areFeetTracked(); - if (myAvatar.getHMDLeanRecenterEnabled()) { // Rotation recenter @@ -5763,8 +5785,8 @@ void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, // Horizontal and rotation recenter - if ((feetAreTracked || getForceActivateHorizontal()) && !isActive(CharacterController::FollowType::Horizontal)) { - activate(CharacterController::FollowType::Horizontal, feetAreTracked); + if ((myAvatar.areFeetTracked() || getForceActivateHorizontal()) && !isActive(CharacterController::FollowType::Horizontal)) { + activate(CharacterController::FollowType::Horizontal, myAvatar.areFeetTracked()); setForceActivateHorizontal(false); } else { if ((myAvatar.getAllowAvatarLeaningPreference() != MyAvatar::AllowAvatarLeaningPreference::AlwaysNoRecenter) && @@ -7030,21 +7052,10 @@ bool MyAvatar::isAllowedToLean() const { !getIsInSittingState()); } -// Determine if the feet are under direct control (tracked). -bool MyAvatar::areFeetTracked() const { - // Foot tracking only activates when both feet are tracked, so we only need to test one. - return getControllerPoseInSensorFrame(controller::Action::LEFT_FOOT).isValid(); -} - -// Determine if the hips are under direct control (tracked). -bool MyAvatar::areHipsTracked() const { - return getControllerPoseInSensorFrame(controller::Action::HIPS).isValid(); -} - // Determine if crouch recentering is enabled (making the avatar stand when the user is sitting in the real world). bool MyAvatar::getHMDCrouchRecenterEnabled() const { return (!_characterController.getSeated() && - (_allowAvatarStandingPreference.get() == AllowAvatarStandingPreference::Always) && !areFeetTracked()); + (_allowAvatarStandingPreference.get() == AllowAvatarStandingPreference::Always) && !_isBodyPartTracked._feet); } bool MyAvatar::setPointAt(const glm::vec3& pointAtTarget) { diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index c06a1a88ab..99d5759011 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -2008,8 +2008,8 @@ public: bool isJumping(); bool getHMDCrouchRecenterEnabled() const; bool isAllowedToLean() const; - bool areFeetTracked() const; - bool areHipsTracked() const; + bool areFeetTracked() const { return _isBodyPartTracked._feet; }; // Determine if the feet are under direct control. + bool areHipsTracked() const { return _isBodyPartTracked._hips; }; // Determine if the hips are under direct control. public slots: @@ -2730,6 +2730,15 @@ private: bool _isBraking { false }; bool _isAway { false }; + // Indicates which parts of the body are under direct control (tracked). + struct { + bool _feet{ false }; // Left or right foot. + bool _hips{ false }; + bool _leftHand{ false }; + bool _rightHand{ false }; + bool _head{ false }; + } _isBodyPartTracked; + float _boomLength { ZOOM_DEFAULT }; float _yawSpeed; // degrees/sec float _pitchSpeed; // degrees/sec From 1e54d41f958a5e1023ab6241c89e85b9f6722915 Mon Sep 17 00:00:00 2001 From: Phil Palmer Date: Mon, 28 Dec 2020 05:37:06 -0500 Subject: [PATCH 10/28] Remove static variable from MyAvatar::update (made it a member). --- interface/src/avatar/MyAvatar.cpp | 9 +++------ interface/src/avatar/MyAvatar.h | 1 + 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 86c6bcd0d8..a47cf088c6 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -676,12 +676,9 @@ void MyAvatar::update(float deltaTime) { } // Recenter the body when foot tracking starts or ends. - { - static bool prevFeetWereTracked = _isBodyPartTracked._feet; - if (_isBodyPartTracked._feet != prevFeetWereTracked) { - centerBodyInternal(false); - prevFeetWereTracked = _isBodyPartTracked._feet; - } + if (_isBodyPartTracked._feet != _isBodyPartTracked._feetPreviousUpdate) { + centerBodyInternal(false); + _isBodyPartTracked._feetPreviousUpdate = _isBodyPartTracked._feet; } // put the average hand azimuth into sensor space. diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 99d5759011..84913d0ad5 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -2733,6 +2733,7 @@ private: // Indicates which parts of the body are under direct control (tracked). struct { bool _feet{ false }; // Left or right foot. + bool _feetPreviousUpdate{ false };// Value of _feet on the previous update. bool _hips{ false }; bool _leftHand{ false }; bool _rightHand{ false }; From 7e5680b2a6ecd1f9179f0e5e6f3ad63e62e1e132 Mon Sep 17 00:00:00 2001 From: Phil <35943148+Phil-Palmer@users.noreply.github.com> Date: Tue, 29 Dec 2020 18:25:17 -0500 Subject: [PATCH 11/28] Apply suggestions from code review Tidying (bracing style, spaces, remove final comma from enum, fix JSDoc comment). Co-authored-by: David Rowe --- interface/src/avatar/MyAvatar.cpp | 6 ++---- interface/src/avatar/MyAvatar.h | 9 ++++----- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index a47cf088c6..1897b4e55b 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -5622,8 +5622,7 @@ void MyAvatar::FollowHelper::decrementTimeRemaining(float dt) { for (auto& time : _timeRemaining) { if (time == CharacterController::FOLLOW_TIME_IMMEDIATE_SNAP) { time = 0.0f; - } - else { + } else { time -= dt; } } @@ -5638,8 +5637,7 @@ bool MyAvatar::FollowHelper::shouldActivateRotation(const MyAvatar& myAvatar, if (myAvatar.areHipsTracked()) { shouldSnapOut = true; return true; - } - else { + } else { shouldSnapOut = false; } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 84913d0ad5..f424e3ba2c 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -282,7 +282,7 @@ class MyAvatar : public Avatar { *

Warning: Setting this value also sets the value of analogPlusSprintSpeed to twice * the value.

* @property {number} analogPlusSprintSpeed - The sprint (run) speed of your avatar for the "AnalogPlus" control scheme. - * @property {number} isInSittingState - true if the user wearing the HMD is determined to be sitting; + * @property {boolean} isInSittingState - true if the user wearing the HMD is determined to be sitting; * false if the user wearing the HMD is determined to be standing. This can affect whether the avatar * is allowed to stand, lean or recenter its footing, depending on user preferences. * The property value automatically updates as the user sits or stands. Setting the property value overrides the current @@ -543,12 +543,11 @@ public: Q_ENUM(SitStandModelType) // Note: The option strings in setupPreferences (PreferencesDialog.cpp) must match this order. - enum class AllowAvatarStandingPreference : uint - { + enum class AllowAvatarStandingPreference : uint { WhenUserIsStanding, Always, Count, - Default = Always, + Default = Always }; Q_ENUM(AllowAvatarStandingPreference) @@ -2732,7 +2731,7 @@ private: // Indicates which parts of the body are under direct control (tracked). struct { - bool _feet{ false }; // Left or right foot. + bool _feet { false }; // Left or right foot. bool _feetPreviousUpdate{ false };// Value of _feet on the previous update. bool _hips{ false }; bool _leftHand{ false }; From d49cdfff6bdc35166fd8ffa33fafc8ed4530552a Mon Sep 17 00:00:00 2001 From: Phil Palmer Date: Tue, 29 Dec 2020 18:31:42 -0500 Subject: [PATCH 12/28] Remove accidental change of style in a variable declaration. --- interface/src/avatar/MyAvatar.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 1897b4e55b..f4e16f2ee3 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -3821,8 +3821,7 @@ glm::vec3 MyAvatar::calculateScaledDirection() { // compute action input // Determine if we're head or controller relative... - glm::vec3 forward; - glm::vec3 right; + glm::vec3 forward, right; const int movementReference = getMovementReference(); CameraMode cameraMode = qApp->getCamera().getMode(); From 8c7c91ed6fad981f098953329e4d0d8bf4809812 Mon Sep 17 00:00:00 2001 From: Phil Palmer Date: Tue, 29 Dec 2020 19:31:35 -0500 Subject: [PATCH 13/28] Remove const from variable declarations as suggested in the review. Suggested here: https://github.com/vircadia/vircadia/pull/928/files#r549830690 The ones I've left are either - values known at compile time, eg. const float MIN_LENGTH_FOR_NORMALIZE = 0.061f; - consts that were already there in the previous code, eg. const float MAX_DISPLACEMENT = 0.5f * _radius; --- interface/src/avatar/MyAvatar.cpp | 30 +++++++++---------- interface/src/avatar/MySkeletonModel.cpp | 2 +- interface/src/ui/PreferencesDialog.cpp | 8 ++--- libraries/animation/src/Rig.cpp | 4 +-- libraries/physics/src/CharacterController.cpp | 6 ++-- 5 files changed, 25 insertions(+), 25 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index f4e16f2ee3..c374bd9d25 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -3823,31 +3823,31 @@ glm::vec3 MyAvatar::calculateScaledDirection() { // Determine if we're head or controller relative... glm::vec3 forward, right; - const int movementReference = getMovementReference(); + int movementReference = getMovementReference(); CameraMode cameraMode = qApp->getCamera().getMode(); bool vectorsAreInAvatarFrame = true; bool removeLocalYComponent = false; - const bool HMDHandRelativeMovement = + bool HMDHandRelativeMovement = qApp->isHMDMode() && (movementReference == LocomotionRelativeMovementMode::MOVEMENT_HAND_RELATIVE || movementReference == LocomotionRelativeMovementMode::MOVEMENT_HAND_RELATIVE_LEVELED); - const bool desktopLookatOrSelfieMode = + bool desktopLookatOrSelfieMode = !qApp->isHMDMode() && (cameraMode == CAMERA_MODE_FIRST_PERSON_LOOK_AT || cameraMode == CAMERA_MODE_LOOK_AT || cameraMode == CAMERA_MODE_SELFIE); - const bool hoveringOrCollisionless = _characterController.getState() == CharacterController::State::Hover || + bool hoveringOrCollisionless = _characterController.getState() == CharacterController::State::Hover || _characterController.computeCollisionMask() == BULLET_COLLISION_MASK_COLLISIONLESS; if (HMDHandRelativeMovement) { - const controller::Action directionHand = + controller::Action directionHand = (getDominantHand() == DOMINANT_RIGHT_HAND) ? controller::Action::LEFT_HAND : controller::Action::RIGHT_HAND; - const controller::Pose handPoseInAvatarFrame = getControllerPoseInAvatarFrame(directionHand); + controller::Pose handPoseInAvatarFrame = getControllerPoseInAvatarFrame(directionHand); if (handPoseInAvatarFrame.isValid()) { - const glm::vec3 controllerForward(0.0f, 1.0f, 0.0f); - const glm::vec3 controllerRight(0.0f, 0.0f, (directionHand == controller::Action::LEFT_HAND) ? 1.0f : -1.0f); + glm::vec3 controllerForward(0.0f, 1.0f, 0.0f); + glm::vec3 controllerRight(0.0f, 0.0f, (directionHand == controller::Action::LEFT_HAND) ? 1.0f : -1.0f); forward = (handPoseInAvatarFrame.rotation * controllerForward); right = (handPoseInAvatarFrame.rotation * controllerRight); @@ -3887,7 +3887,7 @@ glm::vec3 MyAvatar::calculateScaledDirection() { auto removeYAndNormalize = [](glm::vec3& vector) { vector.y = 0.f; // Normalize if the remaining components are large enough to get a reliable direction. - const float length = glm::length(vector); + float length = glm::length(vector); const float MIN_LENGTH_FOR_NORMALIZE = 0.061f; // sin(3.5 degrees) if (length > MIN_LENGTH_FOR_NORMALIZE) { vector /= length; @@ -4843,16 +4843,16 @@ glm::mat4 MyAvatar::deriveBodyFromHMDSensor(const bool forceFollowYPos) const { glm::vec3 headToNeck = headOrientation * Quaternions::Y_180 * (localNeck - localHead); glm::vec3 neckToRoot = headOrientationYawOnly * Quaternions::Y_180 * -localNeck; - const float worldToSensorScale = getUserEyeHeight() / getEyeHeight(); + float worldToSensorScale = getUserEyeHeight() / getEyeHeight(); glm::vec3 bodyPos = headPosition + worldToSensorScale * (headToNeck + neckToRoot); glm::quat bodyQuat; - const controller::Pose hipsControllerPose = getControllerPoseInSensorFrame(controller::Action::HIPS); + controller::Pose hipsControllerPose = getControllerPoseInSensorFrame(controller::Action::HIPS); if (hipsControllerPose.isValid()) { - const glm::quat hipsOrientation = hipsControllerPose.rotation * Quaternions::Y_180; - const glm::quat hipsOrientationYawOnly = cancelOutRollAndPitch(hipsOrientation); + glm::quat hipsOrientation = hipsControllerPose.rotation * Quaternions::Y_180; + glm::quat hipsOrientationYawOnly = cancelOutRollAndPitch(hipsOrientation); - const glm::vec3 hipsPos = hipsControllerPose.getTranslation(); + glm::vec3 hipsPos = hipsControllerPose.getTranslation(); bodyPos.x = hipsPos.x; bodyPos.z = hipsPos.z; @@ -4863,7 +4863,7 @@ glm::mat4 MyAvatar::deriveBodyFromHMDSensor(const bool forceFollowYPos) const { if (!forceFollowYPos && !getHMDCrouchRecenterEnabled()) { // Set the body's vertical position as if it were standing in its T-pose. - const float userToRigScale = getUserEyeHeight() / getUnscaledEyeHeight(); + float userToRigScale = getUserEyeHeight() / getUnscaledEyeHeight(); bodyPos.y = userToRigScale * rig.getUnscaledHipsHeight(); } diff --git a/interface/src/avatar/MySkeletonModel.cpp b/interface/src/avatar/MySkeletonModel.cpp index 2d33ee35ca..7413e4ece7 100755 --- a/interface/src/avatar/MySkeletonModel.cpp +++ b/interface/src/avatar/MySkeletonModel.cpp @@ -65,7 +65,7 @@ static AnimPose computeHipsInSensorFrame(MyAvatar* myAvatar, bool isFlying) { return result; } - const bool useCenterOfGravityModel = + bool useCenterOfGravityModel = myAvatar->getCenterOfGravityModelEnabled() && !isFlying && !myAvatar->getIsInWalkingState() && !myAvatar->getIsInSittingState() && myAvatar->getHMDLeanRecenterEnabled() && (myAvatar->getAllowAvatarLeaningPreference() != MyAvatar::AllowAvatarLeaningPreference::AlwaysNoRecenter) && diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index 12615426f1..9c53060f31 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -422,11 +422,11 @@ void setupPreferences() { preferences->addPreference(preference); } { - const IntPreference::Getter getter = [myAvatar]() -> int { + IntPreference::Getter getter = [myAvatar]() -> int { return static_cast(myAvatar->getAllowAvatarStandingPreference()); }; - const IntPreference::Setter setter = [myAvatar](const int& value) { + IntPreference::Setter setter = [myAvatar](const int& value) { myAvatar->setAllowAvatarStandingPreference(static_cast(value)); }; @@ -440,11 +440,11 @@ void setupPreferences() { preferences->addPreference(preference); } { - const IntPreference::Getter getter = [myAvatar]() -> int { + IntPreference::Getter getter = [myAvatar]() -> int { return static_cast(myAvatar->getAllowAvatarLeaningPreference()); }; - const IntPreference::Setter setter = [myAvatar](const int& value) { + IntPreference::Setter setter = [myAvatar](const int& value) { myAvatar->setAllowAvatarLeaningPreference(static_cast(value)); }; diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 8b49334925..6dc378a32f 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -2805,9 +2805,9 @@ float Rig::getUnscaledEyeHeight() const { // Get the vertical position of the hips joint, in the rig coordinate frame, ignoring the avatar scale. float Rig::getUnscaledHipsHeight() const { // This factor can be used to scale distances in the geometry frame into the unscaled rig frame. - const float scaleFactor = GetScaleFactorGeometryToUnscaledRig(); + float scaleFactor = GetScaleFactorGeometryToUnscaledRig(); - const int hipsJoint = indexOfJoint("Hips"); + int hipsJoint = indexOfJoint("Hips"); // Values from the skeleton are in the geometry coordinate frame. if (hipsJoint >= 0) { diff --git a/libraries/physics/src/CharacterController.cpp b/libraries/physics/src/CharacterController.cpp index 04ccd9e547..777a3d3f87 100755 --- a/libraries/physics/src/CharacterController.cpp +++ b/libraries/physics/src/CharacterController.cpp @@ -370,8 +370,8 @@ void CharacterController::playerStep(btCollisionWorld* collisionWorld, btScalar btVector3 linearDisplacement(0.0f, 0.0f, 0.0f); { - const float horizontalTime = _followTimeRemainingPerType[static_cast(FollowType::Horizontal)]; - const float verticalTime = _followTimeRemainingPerType[static_cast(FollowType::Vertical)]; + float horizontalTime = _followTimeRemainingPerType[static_cast(FollowType::Horizontal)]; + float verticalTime = _followTimeRemainingPerType[static_cast(FollowType::Vertical)]; if (horizontalTime == FOLLOW_TIME_IMMEDIATE_SNAP) { linearDisplacement.setX(deltaPos.x()); @@ -402,7 +402,7 @@ void CharacterController::playerStep(btCollisionWorld* collisionWorld, btScalar // startRot as default rotation btQuaternion endRot = startRot; - const float rotationTime = _followTimeRemainingPerType[static_cast(FollowType::Rotation)]; + float rotationTime = _followTimeRemainingPerType[static_cast(FollowType::Rotation)]; if (rotationTime > MINIMUM_TIME_REMAINING) { btQuaternion desiredRot = _followDesiredBodyTransform.getRotation(); From cba79c72f51338f80674c27649ef83801c1a004d Mon Sep 17 00:00:00 2001 From: Phil Palmer Date: Wed, 30 Dec 2020 01:01:35 -0500 Subject: [PATCH 14/28] Re-added API features that are no longer used internally; marked them as deprecated. As suggested in the review here: https://github.com/vircadia/vircadia/pull/928/files#r549821976 Re-added and deprecated MyAvatar.userRecenterModel. Retained the functionality of setUserRecenterModel, and approximated that of getUserRecenterModel (some stand+lean preference pairs had no equivalent before). Re-added and deprecated MyAvatar.isSitStandStateLocked. Approximated the functionality of getIsSitStandStateLocked. Didn't retain that of setIsSitStandStateLocked, because it wouldn't be useful on its own; someone using it would probably want setUserRecenterModel instead (or new functions to set the standing and leaning preferences). isSitStandStateLocked's reason to exist was that we could stop keeping track of the user's real-world sit/stand state (in updateSitStandState), and instead pretend the user was always standing (for SitStandModelType::ForceStand) or always sitting (for SitStandModelType::ForceSit). That determined whether the avatar was allowed to lean (wouldn't lean if the user was sitting or in ForceSit). Now though, the user explicitly chooses when the avatar may lean: never / just when the user is standing / even when the user is sitting. These API features were removed in 2179c153de8bd8d1b455894e9ccd3699df133c3f ("VR fixes for: couldn't sit on the floor, wrong walk directions"). --- interface/src/avatar/MyAvatar.cpp | 60 +++++++++++++++++++++++++++++++ interface/src/avatar/MyAvatar.h | 26 ++++++++++++++ 2 files changed, 86 insertions(+) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index c374bd9d25..5349b82a0b 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -5273,6 +5273,33 @@ bool MyAvatar::getIsInSittingState() const { return _isInSittingState.get(); } +// Deprecated, will be removed. +MyAvatar::SitStandModelType MyAvatar::getUserRecenterModel() const { + qCDebug(interfaceapp) + << "MyAvatar.getUserRecenterModel is deprecated and will be removed. If you need it, please contact the developers."; + + // The legacy SitStandModelType corresponding to each AllowAvatarLeaningPreference. + std::array(AllowAvatarLeaningPreference::Count)> legacySitStandModels = { + SitStandModelType::Auto, // AllowAvatarLeaningPreference::WhenUserIsStanding + SitStandModelType::ForceStand, // AllowAvatarLeaningPreference::Always + SitStandModelType::ForceSit, // AllowAvatarLeaningPreference::Never + SitStandModelType::DisableHMDLean // AllowAvatarLeaningPreference::AlwaysNoRecenter + }; + + return legacySitStandModels[static_cast(_allowAvatarLeaningPreference.get())]; +} + +// Deprecated, will be removed. +bool MyAvatar::getIsSitStandStateLocked() const { + qCDebug(interfaceapp) << "MyAvatar.getIsSitStandStateLocked is deprecated and will be removed. If you need it, please " + "contact the developers. See also: MyAvatar.getUserRecenterModel."; + + // In the old code, the record of the user's sit/stand state was locked except when using + // SitStandModelType::Auto or SitStandModelType::DisableHMDLean. + return (_allowAvatarStandingPreference.get() != AllowAvatarStandingPreference::WhenUserIsStanding) && + (_allowAvatarLeaningPreference.get() != AllowAvatarLeaningPreference::AlwaysNoRecenter); +} + // Get the user preference of when MyAvatar may stand. MyAvatar::AllowAvatarStandingPreference MyAvatar::getAllowAvatarStandingPreference() const { return _allowAvatarStandingPreference.get(); @@ -5344,6 +5371,32 @@ void MyAvatar::setIsInSittingState(bool isSitting) { setSitStandStateChange(true); } +// Deprecated, will be removed. +void MyAvatar::setUserRecenterModel(MyAvatar::SitStandModelType modelName) { + qCDebug(interfaceapp) + << "MyAvatar.setUserRecenterModel is deprecated and will be removed. If you need it, please contact the developers."; + + switch (modelName) { + case SitStandModelType::ForceSit: + setAllowAvatarStandingPreference(AllowAvatarStandingPreference::Always); + setAllowAvatarLeaningPreference(AllowAvatarLeaningPreference::Never); + break; + case SitStandModelType::ForceStand: + setAllowAvatarStandingPreference(AllowAvatarStandingPreference::Always); + setAllowAvatarLeaningPreference(AllowAvatarLeaningPreference::Always); + break; + case SitStandModelType::Auto: + default: + setAllowAvatarStandingPreference(AllowAvatarStandingPreference::Always); + setAllowAvatarLeaningPreference(AllowAvatarLeaningPreference::WhenUserIsStanding); + break; + case SitStandModelType::DisableHMDLean: + setAllowAvatarStandingPreference(AllowAvatarStandingPreference::WhenUserIsStanding); + setAllowAvatarLeaningPreference(AllowAvatarLeaningPreference::AlwaysNoRecenter); + break; + } +} + // Set the user preference of when the avatar may stand. void MyAvatar::setAllowAvatarStandingPreference(const MyAvatar::AllowAvatarStandingPreference preference) { _allowAvatarStandingPreference.set(preference); @@ -5353,6 +5406,13 @@ void MyAvatar::setAllowAvatarStandingPreference(const MyAvatar::AllowAvatarStand centerBodyInternal(false); } +// Deprecated, will be removed. +void MyAvatar::setIsSitStandStateLocked(bool isLocked) { + Q_UNUSED(isLocked); + qCDebug(interfaceapp) << "MyAvatar.setIsSitStandStateLocked is deprecated and will be removed. If you need it, please " + "contact the developers. See also: MyAvatar.setUserRecenterModel."; +} + // Set the user preference of when the avatar may lean. void MyAvatar::setAllowAvatarLeaningPreference(const MyAvatar::AllowAvatarLeaningPreference preference) { _allowAvatarLeaningPreference.set(preference); diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index f424e3ba2c..b773f802d2 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -282,11 +282,18 @@ class MyAvatar : public Avatar { *

Warning: Setting this value also sets the value of analogPlusSprintSpeed to twice * the value.

* @property {number} analogPlusSprintSpeed - The sprint (run) speed of your avatar for the "AnalogPlus" control scheme. + * @property {MyAvatar.SitStandModelType} userRecenterModel - Controls avatar leaning and recentering behavior. + *

Deprecated: This property is deprecated and will be removed. If you need it, please contact + * the developers.

* @property {boolean} isInSittingState - true if the user wearing the HMD is determined to be sitting; * false if the user wearing the HMD is determined to be standing. This can affect whether the avatar * is allowed to stand, lean or recenter its footing, depending on user preferences. * The property value automatically updates as the user sits or stands. Setting the property value overrides the current * sitting / standing state, which is updated when the user next sits or stands. + * @property {boolean} isSitStandStateLocked - true to lock the avatar sitting/standing state, i.e., use this + * to disable automatically changing state. + *

Deprecated: This property is deprecated and will be removed. If you need it, please contact + * the developers. See also: getUserRecenterModel and setUserRecenterModel.

* @property {boolean} allowTeleporting - true if teleporting is enabled in the Interface settings, * false if it isn't. Read-only. * @@ -408,6 +415,19 @@ class MyAvatar : public Avatar { Q_PROPERTY(float walkBackwardSpeed READ getWalkBackwardSpeed WRITE setWalkBackwardSpeed NOTIFY walkBackwardSpeedChanged); Q_PROPERTY(float sprintSpeed READ getSprintSpeed WRITE setSprintSpeed NOTIFY sprintSpeedChanged); Q_PROPERTY(bool isInSittingState READ getIsInSittingState WRITE setIsInSittingState); + + /**jsdoc + * @deprecated This property is deprecated and will be removed. If you need it, please contact the developers. + */ + Q_PROPERTY(MyAvatar::SitStandModelType userRecenterModel READ getUserRecenterModel WRITE setUserRecenterModel); + + /**jsdoc + * @deprecated This property is deprecated and will be removed. If you need it, please contact the developers. + * See also: {@link MyAvatar.getUserRecenterModel|getUserRecenterModel} and + * {@link MyAvatar.setUserRecenterModel|setUserRecenterModel}. + */ + Q_PROPERTY(bool isSitStandStateLocked READ getIsSitStandStateLocked WRITE setIsSitStandStateLocked); + Q_PROPERTY(bool allowTeleporting READ getAllowTeleporting) const QString DOMINANT_LEFT_HAND = "left"; @@ -512,6 +532,8 @@ public: /**jsdoc *

Specifies different avatar leaning and recentering behaviors.

+ *

Deprecated: This enumeration is deprecated and will be removed. If you need it, please contact + * the developers

* * * @@ -1769,6 +1791,10 @@ public: bool getIsInWalkingState() const; void setIsInSittingState(bool isSitting); bool getIsInSittingState() const; + void setUserRecenterModel(MyAvatar::SitStandModelType modelName); // Deprecated, will be removed. + MyAvatar::SitStandModelType getUserRecenterModel() const; // Deprecated, will be removed. + void setIsSitStandStateLocked(bool isLocked); // Deprecated, will be removed. + bool getIsSitStandStateLocked() const; // Deprecated, will be removed. void setAllowAvatarStandingPreference(const AllowAvatarStandingPreference preference); AllowAvatarStandingPreference getAllowAvatarStandingPreference() const; void setAllowAvatarLeaningPreference(const AllowAvatarLeaningPreference preference); From 8ca64213766232d9eab137c22786c66508206703 Mon Sep 17 00:00:00 2001 From: Phil Palmer Date: Wed, 30 Dec 2020 01:56:05 -0500 Subject: [PATCH 15/28] Correct the name of a scale factor (had inverted meaning). --- interface/src/avatar/MyAvatar.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 5349b82a0b..4a5afbef47 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -4863,8 +4863,8 @@ glm::mat4 MyAvatar::deriveBodyFromHMDSensor(const bool forceFollowYPos) const { if (!forceFollowYPos && !getHMDCrouchRecenterEnabled()) { // Set the body's vertical position as if it were standing in its T-pose. - float userToRigScale = getUserEyeHeight() / getUnscaledEyeHeight(); - bodyPos.y = userToRigScale * rig.getUnscaledHipsHeight(); + float rigToUserScale = getUserEyeHeight() / getUnscaledEyeHeight(); + bodyPos.y = rigToUserScale * rig.getUnscaledHipsHeight(); } glm::mat4 bodyMat = createMatFromQuatAndPos(bodyQuat, bodyPos); From 8cf76609938648437db449e12246c3a0f05ae823 Mon Sep 17 00:00:00 2001 From: Phil Palmer Date: Wed, 30 Dec 2020 02:00:28 -0500 Subject: [PATCH 16/28] Replace a tab with spaces. --- interface/src/avatar/MyAvatar.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index b773f802d2..188220554b 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -423,7 +423,7 @@ class MyAvatar : public Avatar { /**jsdoc * @deprecated This property is deprecated and will be removed. If you need it, please contact the developers. - * See also: {@link MyAvatar.getUserRecenterModel|getUserRecenterModel} and + * See also: {@link MyAvatar.getUserRecenterModel|getUserRecenterModel} and * {@link MyAvatar.setUserRecenterModel|setUserRecenterModel}. */ Q_PROPERTY(bool isSitStandStateLocked READ getIsSitStandStateLocked WRITE setIsSitStandStateLocked); From ee0904d5618d940bccff7562134cf2f0c09edf18 Mon Sep 17 00:00:00 2001 From: Phil Palmer Date: Wed, 30 Dec 2020 04:13:37 -0500 Subject: [PATCH 17/28] Remove MyAvatar "squat" recenter from ad6bbc7ff6954461c35da6ddbdef7b71bf7c5c55 The "squat" recenter pre-dates the sit/stand detection, but the current version basically came from ad6bbc7ff6954461c35da6ddbdef7b71bf7c5c55 in 2018. I'm removing it because it doesn't seem useful anymore (with or without the other changes in this branch). What it did (on the current master) : - If "Avatar leaning behavior" was "Auto" - and the user was standing - but their head was more than 5cm below standing height - and their chest was upright - for 30 seconds - do a vertical recenter so that the avatar stands straight (instead of having knees bent). When the user stood up straight after that recenter, the avatar would hover off the ground until the next recenter (eg. walking). --- interface/src/avatar/MyAvatar.cpp | 33 ------------------------------- interface/src/avatar/MyAvatar.h | 1 - 2 files changed, 34 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 4a5afbef47..bcf5192bed 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -652,8 +652,6 @@ void MyAvatar::update(float deltaTime) { // update moving average of HMD facing in xz plane. const float HMD_FACING_TIMESCALE = getRotationRecenterFilterLength(); const float PERCENTAGE_WEIGHT_HEAD_VS_SHOULDERS_AZIMUTH = 0.0f; // 100 percent shoulders - const float COSINE_THIRTY_DEGREES = 0.866f; - const float SQUATTY_TIMEOUT = 30.0f; // 30 seconds const float HEIGHT_FILTER_COEFFICIENT = 0.01f; float tau = deltaTime / HMD_FACING_TIMESCALE; @@ -720,29 +718,6 @@ void MyAvatar::update(float deltaTime) { setAverageHeadRotation(computeAverageHeadRotation(getControllerPoseInAvatarFrame(controller::Action::HEAD))); } - // if the spine is straight and the head is below the default position by 5 cm then increment squatty count. - const float SQUAT_THRESHOLD = 0.05f; - glm::vec3 headDefaultPositionAvatarSpace = getAbsoluteDefaultJointTranslationInObjectFrame(getJointIndex("Head")); - glm::quat spine2OrientationAvatarSpace = getAbsoluteJointRotationInObjectFrame(getJointIndex("Spine2")); - glm::vec3 upSpine2 = spine2OrientationAvatarSpace * glm::vec3(0.0f, 1.0f, 0.0f); - if (glm::length(upSpine2) > 0.0f) { - upSpine2 = glm::normalize(upSpine2); - } - float angleSpine2 = glm::dot(upSpine2, glm::vec3(0.0f, 1.0f, 0.0f)); - - if (getHMDCrouchRecenterEnabled() && - getControllerPoseInAvatarFrame(controller::Action::HEAD).getTranslation().y < (headDefaultPositionAvatarSpace.y - SQUAT_THRESHOLD) && - (angleSpine2 > COSINE_THIRTY_DEGREES)) { - - _squatTimer += deltaTime; - if (_squatTimer > SQUATTY_TIMEOUT) { - _squatTimer = 0.0f; - _follow._squatDetected = true; - } - } else { - _squatTimer = 0.0f; - } - // put update sit stand state counts here updateSitStandState(newHeightReading.getTranslation().y, deltaTime); @@ -5363,7 +5338,6 @@ void MyAvatar::setIsInWalkingState(bool isWalking) { // Specify whether the user is sitting or standing in the real world. void MyAvatar::setIsInSittingState(bool isSitting) { _sitStandStateTimer = 0.0f; - _squatTimer = 0.0f; // on reset height we need the count to be more than one in case the user sits and stands up quickly. _isInSittingState.set(isSitting); setResetMode(true); @@ -5811,10 +5785,6 @@ bool MyAvatar::FollowHelper::shouldActivateVertical(const MyAvatar& myAvatar, } else { // in the standing state returnValue = (offset.y > CYLINDER_TOP) || (offset.y < CYLINDER_BOTTOM); - // finally check for squats in standing - if (_squatDetected) { - returnValue = true; - } } } return returnValue; @@ -5873,9 +5843,6 @@ void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, if (!isActive(CharacterController::FollowType::Vertical) && (shouldActivateVertical(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) { activate(CharacterController::FollowType::Vertical, false); - if (_squatDetected) { - _squatDetected = false; - } } } } else { diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 188220554b..5fe068a3df 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -2920,7 +2920,6 @@ private: void setForceActivateHorizontal(bool val); bool getToggleHipsFollowing() const; void setToggleHipsFollowing(bool followHead); - bool _squatDetected { false }; std::atomic _forceActivateRotation { false }; std::atomic _forceActivateVertical { false }; std::atomic _forceActivateHorizontal { false }; From 8d3e2ae9eecc456cabc46757c6b64e379427859f Mon Sep 17 00:00:00 2001 From: Phil Palmer Date: Wed, 30 Dec 2020 04:13:37 -0500 Subject: [PATCH 18/28] Remove MyAvatar "squat" recenter from ad6bbc7ff6954461c35da6ddbdef7b71bf7c5c55 The "squat" recenter pre-dates the sit/stand detection, but the current version basically came from ad6bbc7ff6954461c35da6ddbdef7b71bf7c5c55 in 2018. I'm removing it because it doesn't seem useful anymore (with or without the other changes in this branch). What it did (on the current master) : - If "Avatar leaning behavior" was "Auto" - and the user was standing - but their head was more than 5cm below standing height - and their chest was upright - for 30 seconds - do a vertical recenter so that the avatar stands straight (instead of having knees bent). When the user stood up straight after that recenter, the avatar would hover off the ground until the next recenter (eg. walking). --- interface/src/avatar/MyAvatar.cpp | 33 ------------------------------- interface/src/avatar/MyAvatar.h | 2 -- 2 files changed, 35 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 4a5afbef47..bcf5192bed 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -652,8 +652,6 @@ void MyAvatar::update(float deltaTime) { // update moving average of HMD facing in xz plane. const float HMD_FACING_TIMESCALE = getRotationRecenterFilterLength(); const float PERCENTAGE_WEIGHT_HEAD_VS_SHOULDERS_AZIMUTH = 0.0f; // 100 percent shoulders - const float COSINE_THIRTY_DEGREES = 0.866f; - const float SQUATTY_TIMEOUT = 30.0f; // 30 seconds const float HEIGHT_FILTER_COEFFICIENT = 0.01f; float tau = deltaTime / HMD_FACING_TIMESCALE; @@ -720,29 +718,6 @@ void MyAvatar::update(float deltaTime) { setAverageHeadRotation(computeAverageHeadRotation(getControllerPoseInAvatarFrame(controller::Action::HEAD))); } - // if the spine is straight and the head is below the default position by 5 cm then increment squatty count. - const float SQUAT_THRESHOLD = 0.05f; - glm::vec3 headDefaultPositionAvatarSpace = getAbsoluteDefaultJointTranslationInObjectFrame(getJointIndex("Head")); - glm::quat spine2OrientationAvatarSpace = getAbsoluteJointRotationInObjectFrame(getJointIndex("Spine2")); - glm::vec3 upSpine2 = spine2OrientationAvatarSpace * glm::vec3(0.0f, 1.0f, 0.0f); - if (glm::length(upSpine2) > 0.0f) { - upSpine2 = glm::normalize(upSpine2); - } - float angleSpine2 = glm::dot(upSpine2, glm::vec3(0.0f, 1.0f, 0.0f)); - - if (getHMDCrouchRecenterEnabled() && - getControllerPoseInAvatarFrame(controller::Action::HEAD).getTranslation().y < (headDefaultPositionAvatarSpace.y - SQUAT_THRESHOLD) && - (angleSpine2 > COSINE_THIRTY_DEGREES)) { - - _squatTimer += deltaTime; - if (_squatTimer > SQUATTY_TIMEOUT) { - _squatTimer = 0.0f; - _follow._squatDetected = true; - } - } else { - _squatTimer = 0.0f; - } - // put update sit stand state counts here updateSitStandState(newHeightReading.getTranslation().y, deltaTime); @@ -5363,7 +5338,6 @@ void MyAvatar::setIsInWalkingState(bool isWalking) { // Specify whether the user is sitting or standing in the real world. void MyAvatar::setIsInSittingState(bool isSitting) { _sitStandStateTimer = 0.0f; - _squatTimer = 0.0f; // on reset height we need the count to be more than one in case the user sits and stands up quickly. _isInSittingState.set(isSitting); setResetMode(true); @@ -5811,10 +5785,6 @@ bool MyAvatar::FollowHelper::shouldActivateVertical(const MyAvatar& myAvatar, } else { // in the standing state returnValue = (offset.y > CYLINDER_TOP) || (offset.y < CYLINDER_BOTTOM); - // finally check for squats in standing - if (_squatDetected) { - returnValue = true; - } } } return returnValue; @@ -5873,9 +5843,6 @@ void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, if (!isActive(CharacterController::FollowType::Vertical) && (shouldActivateVertical(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) { activate(CharacterController::FollowType::Vertical, false); - if (_squatDetected) { - _squatDetected = false; - } } } } else { diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 188220554b..a2a02fa7f8 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -2920,7 +2920,6 @@ private: void setForceActivateHorizontal(bool val); bool getToggleHipsFollowing() const; void setToggleHipsFollowing(bool followHead); - bool _squatDetected { false }; std::atomic _forceActivateRotation { false }; std::atomic _forceActivateVertical { false }; std::atomic _forceActivateHorizontal { false }; @@ -3027,7 +3026,6 @@ private: MyAvatar::AllowAvatarLeaningPreference::Default }; // The user preference of when MyAvatar may lean. float _sitStandStateTimer { 0.0f }; - float _squatTimer { 0.0f }; float _tippingPoint { _userHeight.get() }; // load avatar scripts once when rig is ready From 44324584b4a5ea9a675da7c45283f480061cf985 Mon Sep 17 00:00:00 2001 From: Phil Palmer Date: Wed, 30 Dec 2020 04:30:52 -0500 Subject: [PATCH 19/28] Fix accidental change of bracing style. --- interface/src/avatar/MySkeletonModel.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/interface/src/avatar/MySkeletonModel.cpp b/interface/src/avatar/MySkeletonModel.cpp index 7413e4ece7..e7341ceec6 100755 --- a/interface/src/avatar/MySkeletonModel.cpp +++ b/interface/src/avatar/MySkeletonModel.cpp @@ -75,8 +75,7 @@ static AnimPose computeHipsInSensorFrame(MyAvatar* myAvatar, bool isFlying) { if (useCenterOfGravityModel) { // then we use center of gravity model hipsMat = myAvatar->deriveBodyUsingCgModel(); - } - else { + } else { // otherwise use the default of putting the hips under the head hipsMat = myAvatar->deriveBodyFromHMDSensor(true); } From a95d29d3277927c80c1b9ba64f0de2a291f9815a Mon Sep 17 00:00:00 2001 From: Phil Palmer Date: Wed, 30 Dec 2020 21:36:24 -0500 Subject: [PATCH 20/28] Fix leaning not using center-of-gravity model in mode 'Allow avatar to stand: When I'm standing'. computeHipsInSensorFrame was accidentally setting useCenterOfGravityModel to false if !getHMDCrouchRecenterEnabled. Now it sets it to true if !getHMDCrouchRecenterEnabled. So if artificial standing is disabled, it uses center-of-gravity regardless of the user's sit/stand state. --- interface/src/avatar/MySkeletonModel.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/interface/src/avatar/MySkeletonModel.cpp b/interface/src/avatar/MySkeletonModel.cpp index e7341ceec6..4984c1d335 100755 --- a/interface/src/avatar/MySkeletonModel.cpp +++ b/interface/src/avatar/MySkeletonModel.cpp @@ -65,11 +65,13 @@ static AnimPose computeHipsInSensorFrame(MyAvatar* myAvatar, bool isFlying) { return result; } + // Use the center-of-gravity model if the user and the avatar are standing, unless flying or walking. + // If artificial standing is disabled, use center-of-gravity regardless of the user's sit/stand state. bool useCenterOfGravityModel = myAvatar->getCenterOfGravityModelEnabled() && !isFlying && !myAvatar->getIsInWalkingState() && - !myAvatar->getIsInSittingState() && myAvatar->getHMDLeanRecenterEnabled() && - (myAvatar->getAllowAvatarLeaningPreference() != MyAvatar::AllowAvatarLeaningPreference::AlwaysNoRecenter) && - myAvatar->getHMDCrouchRecenterEnabled(); + (!myAvatar->getHMDCrouchRecenterEnabled() || !myAvatar->getIsInSittingState()) && + myAvatar->getHMDLeanRecenterEnabled() && + (myAvatar->getAllowAvatarLeaningPreference() != MyAvatar::AllowAvatarLeaningPreference::AlwaysNoRecenter); glm::mat4 hipsMat; if (useCenterOfGravityModel) { From dd2a11f53b0ab0b34adc5c8caab16c337c4c0f7f Mon Sep 17 00:00:00 2001 From: Phil Palmer Date: Wed, 30 Dec 2020 22:38:31 -0500 Subject: [PATCH 21/28] Decouple MyAvatar.centerOfGravityModelEnabled from the user standing state. Engine code no longer controls MyAvatar.centerOfGravityModelEnabled. Maybe we should deprecate it now, since its reason to exist seemed to be only to disable the CG model while the user was sitting, which is now done more explicitly (see below). MyAvatar::FollowHelper: - rename shouldActivateHorizontal to shouldActivateHorizontal_userSitting, now private. - rename shouldActivateHorizontalCG to shouldActivateHorizontal_userStanding, now private. - add new shouldActivateHorizontal that calls one of the above based on the user's sit/stand state. - these functions no longer modify their 'myAvatar' parameter. Add USER_CAN_TURN_BODY_WHILE_SITTING (false), which retains the old rotation behaviour of lean recentering: a lean recenter doesn't rotate the body if the user is sitting (new: unless the feet are tracked). In other words, the lean recentering assumes the user isn't on a swivel chair and keeps the avatar pointing in the same direction. It might be good to expose that as an option. (Regardless, rotation recentering does kick-in if they turn too far). --- interface/src/avatar/MyAvatar.cpp | 87 +++++++++++++++++++++---------- interface/src/avatar/MyAvatar.h | 17 ++++-- 2 files changed, 73 insertions(+), 31 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index bcf5192bed..fc2394cfc0 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -4619,7 +4619,7 @@ bool MyAvatar::getFlyingHMDPref() { } // Public interface for targetscale -float MyAvatar::getAvatarScale() { +float MyAvatar::getAvatarScale() const { return getTargetScale(); } @@ -5341,7 +5341,6 @@ void MyAvatar::setIsInSittingState(bool isSitting) { // on reset height we need the count to be more than one in case the user sits and stands up quickly. _isInSittingState.set(isSitting); setResetMode(true); - setCenterOfGravityModelEnabled(!isSitting); setSitStandStateChange(true); } @@ -5679,7 +5678,10 @@ bool MyAvatar::FollowHelper::shouldActivateRotation(const MyAvatar& myAvatar, return glm::dot(-myAvatar.getHeadControllerFacingMovingAverage(), bodyFacing) < FOLLOW_ROTATION_THRESHOLD; } -bool MyAvatar::FollowHelper::shouldActivateHorizontal(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const { +// Determine if the horizontal following should activate, for a user who is sitting in the real world. +bool MyAvatar::FollowHelper::shouldActivateHorizontal_userSitting(const MyAvatar& myAvatar, + const glm::mat4& desiredBodyMatrix, + const glm::mat4& currentBodyMatrix) const { if (!myAvatar.isAllowedToLean()) { controller::Pose currentHeadPose = myAvatar.getControllerPoseInAvatarFrame(controller::Action::HEAD); if (!withinBaseOfSupport(currentHeadPose)) { @@ -5711,13 +5713,21 @@ bool MyAvatar::FollowHelper::shouldActivateHorizontal(const MyAvatar& myAvatar, return stepDetected; } -bool MyAvatar::FollowHelper::shouldActivateHorizontalCG(MyAvatar& myAvatar) const { +// Determine if the horizontal following should activate, for a user who is standing in the real world. +// resetModeOut: (out) true if setResetMode(true) should be called if this function returns true. +// currentHeadPoseForWalkingStateOut: (out) the head pose, in the avatar frame, +// that can be tested in order to trigger walking state if this function returns true. +bool MyAvatar::FollowHelper::shouldActivateHorizontal_userStanding( + const MyAvatar& myAvatar, + bool& resetModeOut, + controller::Pose& currentHeadPoseForWalkingStateOut) const { if (myAvatar.getIsInWalkingState()) { return true; } - controller::Pose currentHeadPose = myAvatar.getControllerPoseInAvatarFrame(controller::Action::HEAD); + currentHeadPoseForWalkingStateOut = myAvatar.getControllerPoseInAvatarFrame(controller::Action::HEAD); + controller::Pose& currentHeadPose = currentHeadPoseForWalkingStateOut; bool stepDetected = false; if (!withinBaseOfSupport(currentHeadPose)) { @@ -5750,20 +5760,30 @@ bool MyAvatar::FollowHelper::shouldActivateHorizontalCG(MyAvatar& myAvatar) cons float anatomicalHeadToHipsDistance = glm::length(defaultHeadPosition - defaultHipsPosition); if (!isActive(CharacterController::FollowType::Horizontal) && (!isActive(CharacterController::FollowType::Vertical)) && (glm::length(currentHeadPosition - defaultHipsPosition) > (anatomicalHeadToHipsDistance + (DEFAULT_AVATAR_SPINE_STRETCH_LIMIT * anatomicalHeadToHipsDistance)))) { - myAvatar.setResetMode(true); + resetModeOut = true; stepDetected = true; } } - if (stepDetected) { - if (glm::length(currentHeadPose.velocity) > DEFAULT_AVATAR_WALK_SPEED_THRESHOLD) { - myAvatar.setIsInWalkingState(true); - } - } - return stepDetected; } +// Determine if the horizontal following should activate. +// resetModeOut: (out) true if setResetMode(true) should be called if this function returns true. +// currentHeadPoseForWalkingStateOut: (out) the head pose, in the avatar frame, +// that can be tested in order to trigger walking state if this function returns true. +bool MyAvatar::FollowHelper::shouldActivateHorizontal(const MyAvatar& myAvatar, + const glm::mat4& desiredBodyMatrix, + const glm::mat4& currentBodyMatrix, + bool& resetModeOut, + controller::Pose& currentHeadPoseForWalkingStateOut) const { + if (myAvatar.getIsInSittingState()) { + return shouldActivateHorizontal_userSitting(myAvatar, desiredBodyMatrix, currentBodyMatrix); + } else { + return shouldActivateHorizontal_userStanding(myAvatar, resetModeOut, currentHeadPoseForWalkingStateOut); + } +} + bool MyAvatar::FollowHelper::shouldActivateVertical(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const { @@ -5807,7 +5827,7 @@ void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, } } - // Horizontal and rotation recenter + // Lean recenter if ((myAvatar.areFeetTracked() || getForceActivateHorizontal()) && !isActive(CharacterController::FollowType::Horizontal)) { activate(CharacterController::FollowType::Horizontal, myAvatar.areFeetTracked()); @@ -5815,22 +5835,33 @@ void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, } else { if ((myAvatar.getAllowAvatarLeaningPreference() != MyAvatar::AllowAvatarLeaningPreference::AlwaysNoRecenter) && qApp->getCamera().getMode() != CAMERA_MODE_MIRROR) { - if (myAvatar.getCenterOfGravityModelEnabled()) { - if (!isActive(CharacterController::FollowType::Horizontal) && (shouldActivateHorizontalCG(myAvatar) || hasDriveInput)) { - activate(CharacterController::FollowType::Horizontal, false); - if (myAvatar.getEnableStepResetRotation()) { - activate(CharacterController::FollowType::Rotation, false); - myAvatar.setHeadControllerFacingMovingAverage(myAvatar.getHeadControllerFacing()); - } + + bool resetModeOut = false; + controller::Pose currentHeadPoseForWalkingStateOut; + + // True if the user can turn their body while sitting (eg. swivel chair). + // Todo?: We could expose this as an option. + // (Regardless, rotation recentering does kick-in if they turn too far). + constexpr bool USER_CAN_TURN_BODY_WHILE_SITTING = false; + + if (!isActive(CharacterController::FollowType::Horizontal) && + (shouldActivateHorizontal(myAvatar, desiredBodyMatrix, currentBodyMatrix, + resetModeOut, currentHeadPoseForWalkingStateOut) || + hasDriveInput)) { + activate(CharacterController::FollowType::Horizontal, false); + if (myAvatar.getEnableStepResetRotation() && + (USER_CAN_TURN_BODY_WHILE_SITTING || !myAvatar.getIsInSittingState())) { + activate(CharacterController::FollowType::Rotation, false); + myAvatar.setHeadControllerFacingMovingAverage(myAvatar.getHeadControllerFacing()); } - } else { - // center of gravity model is not enabled - if (!isActive(CharacterController::FollowType::Horizontal) && - (shouldActivateHorizontal(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) { - activate(CharacterController::FollowType::Horizontal, false); - if (myAvatar.getEnableStepResetRotation() && !myAvatar.getIsInSittingState()) { - activate(CharacterController::FollowType::Rotation, false); - myAvatar.setHeadControllerFacingMovingAverage(myAvatar.getHeadControllerFacing()); + + if (resetModeOut) { + myAvatar.setResetMode(true); + } + + if (currentHeadPoseForWalkingStateOut.isValid()) { + if (glm::length(currentHeadPoseForWalkingStateOut.velocity) > DEFAULT_AVATAR_WALK_SPEED_THRESHOLD) { + myAvatar.setIsInWalkingState(true); } } } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index a2a02fa7f8..cde10619ff 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -1634,7 +1634,7 @@ public: * @function MyAvatar.getAvatarScale * @returns {number} The target scale for the avatar, range 0.0051000.0. */ - Q_INVOKABLE float getAvatarScale(); + Q_INVOKABLE float getAvatarScale() const; /**jsdoc * Sets the target scale of the avatar. The target scale is the desired scale of the avatar without any restrictions on @@ -2908,8 +2908,11 @@ private: void decrementTimeRemaining(float dt); bool shouldActivateRotation(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix, bool& shouldSnapOut) const; bool shouldActivateVertical(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const; - bool shouldActivateHorizontal(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const; - bool shouldActivateHorizontalCG(MyAvatar& myAvatar) const; + bool shouldActivateHorizontal(const MyAvatar& myAvatar, + const glm::mat4& desiredBodyMatrix, + const glm::mat4& currentBodyMatrix, + bool& resetModeOut, + controller::Pose& currentHeadPoseForWalkingStateOut) const; void prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat4& bodySensorMatrix, const glm::mat4& currentBodyMatrix, bool hasDriveInput); glm::mat4 postPhysicsUpdate(MyAvatar& myAvatar, const glm::mat4& currentBodyMatrix); bool getForceActivateRotation() const; @@ -2924,6 +2927,14 @@ private: std::atomic _forceActivateVertical { false }; std::atomic _forceActivateHorizontal { false }; std::atomic _toggleHipsFollowing { true }; + + private: + bool shouldActivateHorizontal_userSitting(const MyAvatar& myAvatar, + const glm::mat4& desiredBodyMatrix, + const glm::mat4& currentBodyMatrix) const; + bool shouldActivateHorizontal_userStanding(const MyAvatar& myAvatar, + bool& resetModeOut, + controller::Pose& currentHeadPoseForWalkingStateOut) const; }; FollowHelper _follow; From c5fe49bc3074b7d2ee02db04af34fdf1a241c357 Mon Sep 17 00:00:00 2001 From: Phil Palmer Date: Wed, 30 Dec 2020 22:38:31 -0500 Subject: [PATCH 22/28] Decouple MyAvatar.centerOfGravityModelEnabled from the user standing state. Engine code no longer controls MyAvatar.centerOfGravityModelEnabled. Maybe we should deprecate it now, since its reason to exist seemed to be only to disable the CG model while the user was sitting, which is now done more explicitly (see below). MyAvatar::FollowHelper: - rename shouldActivateHorizontal to shouldActivateHorizontal_userSitting, now private. - rename shouldActivateHorizontalCG to shouldActivateHorizontal_userStanding, now private. - add new shouldActivateHorizontal that calls one of the above based on the user's sit/stand state. - these functions no longer modify their 'myAvatar' parameter. Add USER_CAN_TURN_BODY_WHILE_SITTING (false), which retains the old rotation behaviour of lean recentering: a lean recenter doesn't rotate the body if the user is sitting (new: unless the feet are tracked). In other words, the lean recentering assumes the user isn't on a swivel chair and keeps the avatar pointing in the same direction. It might be good to expose that as an option. (Regardless, rotation recentering does kick-in if they turn too far). --- interface/src/avatar/MyAvatar.cpp | 92 +++++++++++++++++++++---------- interface/src/avatar/MyAvatar.h | 17 +++++- 2 files changed, 76 insertions(+), 33 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index bcf5192bed..bab57044df 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -4619,7 +4619,7 @@ bool MyAvatar::getFlyingHMDPref() { } // Public interface for targetscale -float MyAvatar::getAvatarScale() { +float MyAvatar::getAvatarScale() const { return getTargetScale(); } @@ -5341,7 +5341,6 @@ void MyAvatar::setIsInSittingState(bool isSitting) { // on reset height we need the count to be more than one in case the user sits and stands up quickly. _isInSittingState.set(isSitting); setResetMode(true); - setCenterOfGravityModelEnabled(!isSitting); setSitStandStateChange(true); } @@ -5679,7 +5678,10 @@ bool MyAvatar::FollowHelper::shouldActivateRotation(const MyAvatar& myAvatar, return glm::dot(-myAvatar.getHeadControllerFacingMovingAverage(), bodyFacing) < FOLLOW_ROTATION_THRESHOLD; } -bool MyAvatar::FollowHelper::shouldActivateHorizontal(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const { +// Determine if the horizontal following should activate, for a user who is sitting in the real world. +bool MyAvatar::FollowHelper::shouldActivateHorizontal_userSitting(const MyAvatar& myAvatar, + const glm::mat4& desiredBodyMatrix, + const glm::mat4& currentBodyMatrix) const { if (!myAvatar.isAllowedToLean()) { controller::Pose currentHeadPose = myAvatar.getControllerPoseInAvatarFrame(controller::Action::HEAD); if (!withinBaseOfSupport(currentHeadPose)) { @@ -5711,13 +5713,19 @@ bool MyAvatar::FollowHelper::shouldActivateHorizontal(const MyAvatar& myAvatar, return stepDetected; } -bool MyAvatar::FollowHelper::shouldActivateHorizontalCG(MyAvatar& myAvatar) const { +// Determine if the horizontal following should activate, for a user who is standing in the real world. +// resetModeOut: (out) true if setResetMode(true) should be called if this function returns true. +// goToWalkingStateOut: (out) true if setIsInWalkingState(true) should be called if this function returns true. +bool MyAvatar::FollowHelper::shouldActivateHorizontal_userStanding( + const MyAvatar& myAvatar, + bool& resetModeOut, + bool& goToWalkingStateOut) const { if (myAvatar.getIsInWalkingState()) { return true; } - controller::Pose currentHeadPose = myAvatar.getControllerPoseInAvatarFrame(controller::Action::HEAD); + controller::Pose currentHeadPose = myAvatar.getControllerPoseInAvatarFrame(controller::Action::HEAD); bool stepDetected = false; if (!withinBaseOfSupport(currentHeadPose)) { @@ -5749,21 +5757,36 @@ bool MyAvatar::FollowHelper::shouldActivateHorizontalCG(MyAvatar& myAvatar) cons glm::vec3 currentHeadPosition = currentHeadPose.getTranslation(); float anatomicalHeadToHipsDistance = glm::length(defaultHeadPosition - defaultHipsPosition); if (!isActive(CharacterController::FollowType::Horizontal) && (!isActive(CharacterController::FollowType::Vertical)) && - (glm::length(currentHeadPosition - defaultHipsPosition) > (anatomicalHeadToHipsDistance + (DEFAULT_AVATAR_SPINE_STRETCH_LIMIT * anatomicalHeadToHipsDistance)))) { - myAvatar.setResetMode(true); + (glm::length(currentHeadPosition - defaultHipsPosition) > + (anatomicalHeadToHipsDistance + (DEFAULT_AVATAR_SPINE_STRETCH_LIMIT * anatomicalHeadToHipsDistance)))) { + resetModeOut = true; stepDetected = true; - } - } - - if (stepDetected) { - if (glm::length(currentHeadPose.velocity) > DEFAULT_AVATAR_WALK_SPEED_THRESHOLD) { - myAvatar.setIsInWalkingState(true); + if (currentHeadPose.isValid()) { + if (glm::length(currentHeadPose.velocity) > DEFAULT_AVATAR_WALK_SPEED_THRESHOLD) { + goToWalkingStateOut = true; + } + } } } return stepDetected; } +// Determine if the horizontal following should activate. +// resetModeOut: (out) true if setResetMode(true) should be called if this function returns true. +// goToWalkingStateOut: (out) true if setIsInWalkingState(true) should be called if this function returns true. +bool MyAvatar::FollowHelper::shouldActivateHorizontal(const MyAvatar& myAvatar, + const glm::mat4& desiredBodyMatrix, + const glm::mat4& currentBodyMatrix, + bool& resetModeOut, + bool& goToWalkingStateOut) const { + if (myAvatar.getIsInSittingState()) { + return shouldActivateHorizontal_userSitting(myAvatar, desiredBodyMatrix, currentBodyMatrix); + } else { + return shouldActivateHorizontal_userStanding(myAvatar, resetModeOut, goToWalkingStateOut); + } +} + bool MyAvatar::FollowHelper::shouldActivateVertical(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const { @@ -5807,7 +5830,7 @@ void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, } } - // Horizontal and rotation recenter + // Lean recenter if ((myAvatar.areFeetTracked() || getForceActivateHorizontal()) && !isActive(CharacterController::FollowType::Horizontal)) { activate(CharacterController::FollowType::Horizontal, myAvatar.areFeetTracked()); @@ -5815,23 +5838,32 @@ void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, } else { if ((myAvatar.getAllowAvatarLeaningPreference() != MyAvatar::AllowAvatarLeaningPreference::AlwaysNoRecenter) && qApp->getCamera().getMode() != CAMERA_MODE_MIRROR) { - if (myAvatar.getCenterOfGravityModelEnabled()) { - if (!isActive(CharacterController::FollowType::Horizontal) && (shouldActivateHorizontalCG(myAvatar) || hasDriveInput)) { - activate(CharacterController::FollowType::Horizontal, false); - if (myAvatar.getEnableStepResetRotation()) { - activate(CharacterController::FollowType::Rotation, false); - myAvatar.setHeadControllerFacingMovingAverage(myAvatar.getHeadControllerFacing()); - } + + bool resetModeOut = false; + bool goToWalkingStateOut = false; + + // True if the user can turn their body while sitting (eg. swivel chair). + // Todo?: We could expose this as an option. + // (Regardless, rotation recentering does kick-in if they turn too far). + constexpr bool USER_CAN_TURN_BODY_WHILE_SITTING = false; + + if (!isActive(CharacterController::FollowType::Horizontal) && + (shouldActivateHorizontal(myAvatar, desiredBodyMatrix, currentBodyMatrix, resetModeOut, + goToWalkingStateOut) || + hasDriveInput)) { + activate(CharacterController::FollowType::Horizontal, false); + if (myAvatar.getEnableStepResetRotation() && + (USER_CAN_TURN_BODY_WHILE_SITTING || !myAvatar.getIsInSittingState())) { + activate(CharacterController::FollowType::Rotation, false); + myAvatar.setHeadControllerFacingMovingAverage(myAvatar.getHeadControllerFacing()); } - } else { - // center of gravity model is not enabled - if (!isActive(CharacterController::FollowType::Horizontal) && - (shouldActivateHorizontal(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) { - activate(CharacterController::FollowType::Horizontal, false); - if (myAvatar.getEnableStepResetRotation() && !myAvatar.getIsInSittingState()) { - activate(CharacterController::FollowType::Rotation, false); - myAvatar.setHeadControllerFacingMovingAverage(myAvatar.getHeadControllerFacing()); - } + + if (resetModeOut) { + myAvatar.setResetMode(true); + } + + if (goToWalkingStateOut) { + myAvatar.setIsInWalkingState(true); } } } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index a2a02fa7f8..a411b1d747 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -1634,7 +1634,7 @@ public: * @function MyAvatar.getAvatarScale * @returns {number} The target scale for the avatar, range 0.0051000.0. */ - Q_INVOKABLE float getAvatarScale(); + Q_INVOKABLE float getAvatarScale() const; /**jsdoc * Sets the target scale of the avatar. The target scale is the desired scale of the avatar without any restrictions on @@ -2908,8 +2908,11 @@ private: void decrementTimeRemaining(float dt); bool shouldActivateRotation(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix, bool& shouldSnapOut) const; bool shouldActivateVertical(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const; - bool shouldActivateHorizontal(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const; - bool shouldActivateHorizontalCG(MyAvatar& myAvatar) const; + bool shouldActivateHorizontal(const MyAvatar& myAvatar, + const glm::mat4& desiredBodyMatrix, + const glm::mat4& currentBodyMatrix, + bool& resetModeOut, + bool& goToWalkingStateOut) const; void prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat4& bodySensorMatrix, const glm::mat4& currentBodyMatrix, bool hasDriveInput); glm::mat4 postPhysicsUpdate(MyAvatar& myAvatar, const glm::mat4& currentBodyMatrix); bool getForceActivateRotation() const; @@ -2924,6 +2927,14 @@ private: std::atomic _forceActivateVertical { false }; std::atomic _forceActivateHorizontal { false }; std::atomic _toggleHipsFollowing { true }; + + private: + bool shouldActivateHorizontal_userSitting(const MyAvatar& myAvatar, + const glm::mat4& desiredBodyMatrix, + const glm::mat4& currentBodyMatrix) const; + bool shouldActivateHorizontal_userStanding(const MyAvatar& myAvatar, + bool& resetModeOut, + bool& goToWalkingStateOut) const; }; FollowHelper _follow; From 4a0bde415b82cc26adceb56d2078a1c3aca91aae Mon Sep 17 00:00:00 2001 From: Phil Palmer Date: Thu, 31 Dec 2020 00:06:25 -0500 Subject: [PATCH 23/28] Fix an old confusing comment from 952b1122713192f70bb5b1df4fb8ae987da16522 --- interface/src/avatar/MyAvatar.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index bab57044df..6f187bdbaf 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -5337,8 +5337,10 @@ void MyAvatar::setIsInWalkingState(bool isWalking) { // Specify whether the user is sitting or standing in the real world. void MyAvatar::setIsInSittingState(bool isSitting) { + // In updateSitStandState, we only change state if this timer is above a threshold (STANDING_TIMEOUT, SITTING_TIMEOUT). + // This avoids changing state if the user sits and stands up quickly. _sitStandStateTimer = 0.0f; - // on reset height we need the count to be more than one in case the user sits and stands up quickly. + _isInSittingState.set(isSitting); setResetMode(true); setSitStandStateChange(true); From 3cc08022fcb8ed6b4aa7b9b6182df5a0af80e144 Mon Sep 17 00:00:00 2001 From: Phil Palmer Date: Thu, 31 Dec 2020 05:09:06 -0500 Subject: [PATCH 24/28] Leaning and lean recentering now work as intended at any avatar scale. Individually tested each of these scale fixes in third-person view. Simplify withinBaseOfSupport. Change input parameter from float& to float (isWithinThresholdHeightMode). Debug: remove unnecessary Y offset that didn't scale properly with the avatar (drawBaseOfSupport). --- interface/src/avatar/MyAvatar.cpp | 64 +++++++++++++------------------ 1 file changed, 27 insertions(+), 37 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 6f187bdbaf..2f55d32385 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -4976,11 +4976,11 @@ glm::vec3 MyAvatar::computeCounterBalance() { glm::vec3 currentCg = (1.0f / totalMass) * sumOfMoments; currentCg.y = 0.0f; // dampening the center of gravity, in effect, limits the value to the perimeter of the base of support - float baseScale = 1.0f; + float baseAndAvatarScale = getAvatarScale(); if (getUserEyeHeight() > 0.0f) { - baseScale = getUserEyeHeight() / DEFAULT_AVATAR_EYE_HEIGHT; + baseAndAvatarScale *= getUserEyeHeight() / DEFAULT_AVATAR_EYE_HEIGHT; } - glm::vec3 desiredCg = dampenCgMovement(currentCg, baseScale); + glm::vec3 desiredCg = dampenCgMovement(currentCg, baseAndAvatarScale); // compute hips position to maintain desiredCg glm::vec3 counterBalancedForHead = (totalMass + DEFAULT_AVATAR_HIPS_MASS) * desiredCg; @@ -4999,9 +4999,10 @@ glm::vec3 MyAvatar::computeCounterBalance() { // this is to be sure that the feet don't lift off the floor. // add 5 centimeters to allow for going up on the toes. - if (counterBalancedCg.y > (tposeHips.y + 0.05f)) { + float maxCounterBalancedCGY = (tposeHips.y + 0.05f) * baseAndAvatarScale; + if (counterBalancedCg.y > maxCounterBalancedCGY) { // if the height is higher than default hips, clamp to default hips - counterBalancedCg.y = tposeHips.y + 0.05f; + counterBalancedCg.y = maxCounterBalancedCGY; } return counterBalancedCg; } @@ -5032,7 +5033,7 @@ static void drawBaseOfSupport(float baseOfSupportScale, float footLocal, glm::ma float clampBack = DEFAULT_AVATAR_SUPPORT_BASE_BACK * baseOfSupportScale; float clampLeft = DEFAULT_AVATAR_SUPPORT_BASE_LEFT * baseOfSupportScale; float clampRight = DEFAULT_AVATAR_SUPPORT_BASE_RIGHT * baseOfSupportScale; - float floor = footLocal + 0.05f; + float floor = footLocal; // transform the base of support corners to world space glm::vec3 frontRight = transformPoint(avatarToWorld, { clampRight, floor, clampFront }); @@ -5063,7 +5064,7 @@ glm::mat4 MyAvatar::deriveBodyUsingCgModel() { glm::mat4 avatarHeadMat = glm::inverse(avatarToWorldMat) * sensorToWorldMat * sensorHeadMat; if (_enableDebugDrawBaseOfSupport) { - float scaleBaseOfSupport = getUserEyeHeight() / DEFAULT_AVATAR_EYE_HEIGHT; + float scaleBaseOfSupport = (getUserEyeHeight() / DEFAULT_AVATAR_EYE_HEIGHT) * getAvatarScale(); glm::vec3 rightFootPositionLocal = getAbsoluteJointTranslationInObjectFrame(_skeletonModel->getRig().indexOfJoint("RightFoot")); drawBaseOfSupport(scaleBaseOfSupport, rightFootPositionLocal.y, avatarToWorldMat); } @@ -5079,26 +5080,16 @@ glm::mat4 MyAvatar::deriveBodyUsingCgModel() { return worldToSensorMat * avatarToWorldMat * avatarHipsMat; } -static bool isInsideLine(const glm::vec3& a, const glm::vec3& b, const glm::vec3& c) { - return (((b.x - a.x) * (c.z - a.z) - (b.z - a.z) * (c.x - a.x)) > 0); -} - -static bool withinBaseOfSupport(const controller::Pose& head) { - float userScale = 1.0f; - - glm::vec3 frontLeft(-DEFAULT_AVATAR_LATERAL_STEPPING_THRESHOLD, 0.0f, -DEFAULT_AVATAR_ANTERIOR_STEPPING_THRESHOLD); - glm::vec3 frontRight(DEFAULT_AVATAR_LATERAL_STEPPING_THRESHOLD, 0.0f, -DEFAULT_AVATAR_ANTERIOR_STEPPING_THRESHOLD); - glm::vec3 backLeft(-DEFAULT_AVATAR_LATERAL_STEPPING_THRESHOLD, 0.0f, DEFAULT_AVATAR_POSTERIOR_STEPPING_THRESHOLD); - glm::vec3 backRight(DEFAULT_AVATAR_LATERAL_STEPPING_THRESHOLD, 0.0f, DEFAULT_AVATAR_POSTERIOR_STEPPING_THRESHOLD); - - bool isWithinSupport = false; - if (head.isValid()) { - bool withinFrontBase = isInsideLine(userScale * frontLeft, userScale * frontRight, head.getTranslation()); - bool withinBackBase = isInsideLine(userScale * backRight, userScale * backLeft, head.getTranslation()); - bool withinLateralBase = (isInsideLine(userScale * frontRight, userScale * backRight, head.getTranslation()) && - isInsideLine(userScale * backLeft, userScale * frontLeft, head.getTranslation())); - isWithinSupport = (withinFrontBase && withinBackBase && withinLateralBase); +static bool withinBaseOfSupport(const controller::Pose& head, const float avatarScale) { + if (!head.isValid()) { + return false; } + + vec3 headPosScaled = head.getTranslation() / avatarScale; + bool isWithinSupport = (headPosScaled.x > -DEFAULT_AVATAR_LATERAL_STEPPING_THRESHOLD) && + (headPosScaled.x < DEFAULT_AVATAR_LATERAL_STEPPING_THRESHOLD) && + (headPosScaled.z > -DEFAULT_AVATAR_ANTERIOR_STEPPING_THRESHOLD) && + (headPosScaled.z < DEFAULT_AVATAR_POSTERIOR_STEPPING_THRESHOLD); return isWithinSupport; } @@ -5114,10 +5105,10 @@ static bool headAngularVelocityBelowThreshold(const controller::Pose& head) { return isBelowThreshold; } -static bool isWithinThresholdHeightMode(const controller::Pose& head, const float& newMode, const float& scale) { +static bool isWithinThresholdHeightMode(const controller::Pose& head, const float newMode, const float avatarScale) { bool isWithinThreshold = true; if (head.isValid()) { - isWithinThreshold = (head.getTranslation().y - newMode) > (DEFAULT_AVATAR_MODE_HEIGHT_STEPPING_THRESHOLD * scale); + isWithinThreshold = head.getTranslation().y > ((DEFAULT_AVATAR_MODE_HEIGHT_STEPPING_THRESHOLD + newMode) * avatarScale); } return isWithinThreshold; } @@ -5198,12 +5189,12 @@ static bool handAngularVelocityBelowThreshold(const controller::Pose& leftHand, (rightHandXZAngularVelocity < DEFAULT_HANDS_ANGULAR_VELOCITY_STEPPING_THRESHOLD)); } -static bool headVelocityGreaterThanThreshold(const controller::Pose& head) { +static bool headVelocityGreaterThanThreshold(const controller::Pose& head, const float avatarScale) { float headVelocityMagnitude = 0.0f; if (head.isValid()) { headVelocityMagnitude = glm::length(head.getVelocity()); } - return headVelocityMagnitude > DEFAULT_HEAD_VELOCITY_STEPPING_THRESHOLD; + return headVelocityMagnitude > (DEFAULT_HEAD_VELOCITY_STEPPING_THRESHOLD * avatarScale); } glm::quat MyAvatar::computeAverageHeadRotation(const controller::Pose& head) { @@ -5686,7 +5677,7 @@ bool MyAvatar::FollowHelper::shouldActivateHorizontal_userSitting(const MyAvatar const glm::mat4& currentBodyMatrix) const { if (!myAvatar.isAllowedToLean()) { controller::Pose currentHeadPose = myAvatar.getControllerPoseInAvatarFrame(controller::Action::HEAD); - if (!withinBaseOfSupport(currentHeadPose)) { + if (!withinBaseOfSupport(currentHeadPose, myAvatar.getAvatarScale())) { return true; } } @@ -5729,23 +5720,22 @@ bool MyAvatar::FollowHelper::shouldActivateHorizontal_userStanding( controller::Pose currentHeadPose = myAvatar.getControllerPoseInAvatarFrame(controller::Action::HEAD); bool stepDetected = false; + float avatarScale = myAvatar.getAvatarScale(); - if (!withinBaseOfSupport(currentHeadPose)) { + if (!withinBaseOfSupport(currentHeadPose, avatarScale)) { if (!myAvatar.isAllowedToLean()) { stepDetected = true; } else { - float myScale = myAvatar.getAvatarScale(); - // get the current readings controller::Pose currentLeftHandPose = myAvatar.getControllerPoseInAvatarFrame(controller::Action::LEFT_HAND); controller::Pose currentRightHandPose = myAvatar.getControllerPoseInAvatarFrame(controller::Action::RIGHT_HAND); controller::Pose currentHeadSensorPose = myAvatar.getControllerPoseInSensorFrame(controller::Action::HEAD); if (headAngularVelocityBelowThreshold(currentHeadPose) && - isWithinThresholdHeightMode(currentHeadSensorPose, myAvatar.getCurrentStandingHeight(), myScale) && + isWithinThresholdHeightMode(currentHeadSensorPose, myAvatar.getCurrentStandingHeight(), avatarScale) && handDirectionMatchesHeadDirection(currentLeftHandPose, currentRightHandPose, currentHeadPose) && handAngularVelocityBelowThreshold(currentLeftHandPose, currentRightHandPose) && - headVelocityGreaterThanThreshold(currentHeadPose) && + headVelocityGreaterThanThreshold(currentHeadPose, avatarScale) && isHeadLevel(currentHeadPose, myAvatar.getAverageHeadRotation())) { // a step is detected stepDetected = true; @@ -5764,7 +5754,7 @@ bool MyAvatar::FollowHelper::shouldActivateHorizontal_userStanding( resetModeOut = true; stepDetected = true; if (currentHeadPose.isValid()) { - if (glm::length(currentHeadPose.velocity) > DEFAULT_AVATAR_WALK_SPEED_THRESHOLD) { + if (glm::length(currentHeadPose.velocity) > (DEFAULT_AVATAR_WALK_SPEED_THRESHOLD * avatarScale)) { goToWalkingStateOut = true; } } From 646a5798e601b68a7d450812f12188b5714ba64d Mon Sep 17 00:00:00 2001 From: Phil <35943148+Phil-Palmer@users.noreply.github.com> Date: Mon, 4 Jan 2021 00:12:57 -0500 Subject: [PATCH 25/28] Apply suggestions from code review Tidying (bracing style, spaces, remove final comma from enum, fix JSDoc comments). Co-authored-by: David Rowe --- interface/src/avatar/MyAvatar.h | 18 ++++++------------ libraries/physics/src/CharacterController.h | 3 +-- 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index a411b1d747..77f964ee3c 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -283,8 +283,7 @@ class MyAvatar : public Avatar { * the value.

* @property {number} analogPlusSprintSpeed - The sprint (run) speed of your avatar for the "AnalogPlus" control scheme. * @property {MyAvatar.SitStandModelType} userRecenterModel - Controls avatar leaning and recentering behavior. - *

Deprecated: This property is deprecated and will be removed. If you need it, please contact - * the developers.

+ *

Deprecated: This property is deprecated and will be removed.

* @property {boolean} isInSittingState - true if the user wearing the HMD is determined to be sitting; * false if the user wearing the HMD is determined to be standing. This can affect whether the avatar * is allowed to stand, lean or recenter its footing, depending on user preferences. @@ -416,10 +415,7 @@ class MyAvatar : public Avatar { Q_PROPERTY(float sprintSpeed READ getSprintSpeed WRITE setSprintSpeed NOTIFY sprintSpeedChanged); Q_PROPERTY(bool isInSittingState READ getIsInSittingState WRITE setIsInSittingState); - /**jsdoc - * @deprecated This property is deprecated and will be removed. If you need it, please contact the developers. - */ - Q_PROPERTY(MyAvatar::SitStandModelType userRecenterModel READ getUserRecenterModel WRITE setUserRecenterModel); + Q_PROPERTY(MyAvatar::SitStandModelType userRecenterModel READ getUserRecenterModel WRITE setUserRecenterModel); // Deprecated /**jsdoc * @deprecated This property is deprecated and will be removed. If you need it, please contact the developers. @@ -532,8 +528,7 @@ public: /**jsdoc *

Specifies different avatar leaning and recentering behaviors.

- *

Deprecated: This enumeration is deprecated and will be removed. If you need it, please contact - * the developers

+ *

Deprecated: This type is deprecated and will be removed.

*
ValueNameDescription
* * @@ -574,14 +569,13 @@ public: Q_ENUM(AllowAvatarStandingPreference) // Note: The option strings in setupPreferences (PreferencesDialog.cpp) must match this order. - enum class AllowAvatarLeaningPreference : uint - { + enum class AllowAvatarLeaningPreference : uint { WhenUserIsStanding, Always, Never, AlwaysNoRecenter, // experimental Count, - Default = WhenUserIsStanding, + Default = WhenUserIsStanding }; Q_ENUM(AllowAvatarLeaningPreference) @@ -2981,7 +2975,7 @@ private: bool _centerOfGravityModelEnabled { true }; bool _hmdLeanRecenterEnabled { true }; - bool _hmdCrouchRecenterEnabled{ + bool _hmdCrouchRecenterEnabled { true }; // Is MyAvatar allowed to recenter vertically (stand) when the user is sitting in the real world. bool _sprint { false }; diff --git a/libraries/physics/src/CharacterController.h b/libraries/physics/src/CharacterController.h index 899c99890b..8242ae4b97 100755 --- a/libraries/physics/src/CharacterController.h +++ b/libraries/physics/src/CharacterController.h @@ -53,8 +53,7 @@ const btScalar MIN_CHARACTER_MOTOR_TIMESCALE = 0.05f; class CharacterController : public btCharacterControllerInterface { public: - enum class FollowType : uint8_t - { + enum class FollowType : uint8_t { Rotation, Horizontal, Vertical, From 5d1bd5cc19ee3110349a588351d7c9c18df62fd4 Mon Sep 17 00:00:00 2001 From: Phil Palmer Date: Mon, 4 Jan 2021 00:57:17 -0500 Subject: [PATCH 26/28] * Apply suggestions from code review Fix JSDoc comments and debug messages. --- interface/src/avatar/MyAvatar.cpp | 7 +++---- interface/src/avatar/MyAvatar.h | 14 +++----------- 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 2f55d32385..908f42c41d 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -5242,7 +5242,7 @@ bool MyAvatar::getIsInSittingState() const { // Deprecated, will be removed. MyAvatar::SitStandModelType MyAvatar::getUserRecenterModel() const { qCDebug(interfaceapp) - << "MyAvatar.getUserRecenterModel is deprecated and will be removed. If you need it, please contact the developers."; + << "MyAvatar.getUserRecenterModel is deprecated and will be removed."; // The legacy SitStandModelType corresponding to each AllowAvatarLeaningPreference. std::array(AllowAvatarLeaningPreference::Count)> legacySitStandModels = { @@ -5340,7 +5340,7 @@ void MyAvatar::setIsInSittingState(bool isSitting) { // Deprecated, will be removed. void MyAvatar::setUserRecenterModel(MyAvatar::SitStandModelType modelName) { qCDebug(interfaceapp) - << "MyAvatar.setUserRecenterModel is deprecated and will be removed. If you need it, please contact the developers."; + << "MyAvatar.setUserRecenterModel is deprecated and will be removed."; switch (modelName) { case SitStandModelType::ForceSit: @@ -5375,8 +5375,7 @@ void MyAvatar::setAllowAvatarStandingPreference(const MyAvatar::AllowAvatarStand // Deprecated, will be removed. void MyAvatar::setIsSitStandStateLocked(bool isLocked) { Q_UNUSED(isLocked); - qCDebug(interfaceapp) << "MyAvatar.setIsSitStandStateLocked is deprecated and will be removed. If you need it, please " - "contact the developers. See also: MyAvatar.setUserRecenterModel."; + qCDebug(interfaceapp) << "MyAvatar.setIsSitStandStateLocked is deprecated and will be removed."; } // Set the user preference of when the avatar may lean. diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 77f964ee3c..3d278cf983 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -291,8 +291,8 @@ class MyAvatar : public Avatar { * sitting / standing state, which is updated when the user next sits or stands. * @property {boolean} isSitStandStateLocked - true to lock the avatar sitting/standing state, i.e., use this * to disable automatically changing state. - *

Deprecated: This property is deprecated and will be removed. If you need it, please contact - * the developers. See also: getUserRecenterModel and setUserRecenterModel.

+ *

Deprecated: This property is deprecated and will be removed. + * See also: getUserRecenterModel and setUserRecenterModel.

* @property {boolean} allowTeleporting - true if teleporting is enabled in the Interface settings, * false if it isn't. Read-only. * @@ -414,16 +414,8 @@ class MyAvatar : public Avatar { Q_PROPERTY(float walkBackwardSpeed READ getWalkBackwardSpeed WRITE setWalkBackwardSpeed NOTIFY walkBackwardSpeedChanged); Q_PROPERTY(float sprintSpeed READ getSprintSpeed WRITE setSprintSpeed NOTIFY sprintSpeedChanged); Q_PROPERTY(bool isInSittingState READ getIsInSittingState WRITE setIsInSittingState); - Q_PROPERTY(MyAvatar::SitStandModelType userRecenterModel READ getUserRecenterModel WRITE setUserRecenterModel); // Deprecated - - /**jsdoc - * @deprecated This property is deprecated and will be removed. If you need it, please contact the developers. - * See also: {@link MyAvatar.getUserRecenterModel|getUserRecenterModel} and - * {@link MyAvatar.setUserRecenterModel|setUserRecenterModel}. - */ - Q_PROPERTY(bool isSitStandStateLocked READ getIsSitStandStateLocked WRITE setIsSitStandStateLocked); - + Q_PROPERTY(bool isSitStandStateLocked READ getIsSitStandStateLocked WRITE setIsSitStandStateLocked); // Deprecated Q_PROPERTY(bool allowTeleporting READ getAllowTeleporting) const QString DOMINANT_LEFT_HAND = "left"; From 9f84b8384e730d2ed7860b525a116b0a6b565f07 Mon Sep 17 00:00:00 2001 From: Phil Palmer Date: Mon, 4 Jan 2021 15:49:26 -0500 Subject: [PATCH 27/28] Apply suggestion from code review: fix JSDoc comment. --- interface/src/avatar/MyAvatar.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 908f42c41d..0f66f3bb41 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -5257,8 +5257,7 @@ MyAvatar::SitStandModelType MyAvatar::getUserRecenterModel() const { // Deprecated, will be removed. bool MyAvatar::getIsSitStandStateLocked() const { - qCDebug(interfaceapp) << "MyAvatar.getIsSitStandStateLocked is deprecated and will be removed. If you need it, please " - "contact the developers. See also: MyAvatar.getUserRecenterModel."; + qCDebug(interfaceapp) << "MyAvatar.getIsSitStandStateLocked is deprecated and will be removed."; // In the old code, the record of the user's sit/stand state was locked except when using // SitStandModelType::Auto or SitStandModelType::DisableHMDLean. From a54a40dc2cb1555b4973489d83e57d449bc19c9c Mon Sep 17 00:00:00 2001 From: Phil Palmer Date: Mon, 4 Jan 2021 15:49:26 -0500 Subject: [PATCH 28/28] Apply suggestion from code review: fix debug message. --- interface/src/avatar/MyAvatar.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 908f42c41d..0f66f3bb41 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -5257,8 +5257,7 @@ MyAvatar::SitStandModelType MyAvatar::getUserRecenterModel() const { // Deprecated, will be removed. bool MyAvatar::getIsSitStandStateLocked() const { - qCDebug(interfaceapp) << "MyAvatar.getIsSitStandStateLocked is deprecated and will be removed. If you need it, please " - "contact the developers. See also: MyAvatar.getUserRecenterModel."; + qCDebug(interfaceapp) << "MyAvatar.getIsSitStandStateLocked is deprecated and will be removed."; // In the old code, the record of the user's sit/stand state was locked except when using // SitStandModelType::Auto or SitStandModelType::DisableHMDLean.
ValueNameDescription