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.
This commit is contained in:
Phil Palmer 2020-12-22 14:22:27 -05:00
parent f1576aba78
commit 2179c153de
12 changed files with 691 additions and 441 deletions

View file

@ -96,10 +96,8 @@ const float CENTIMETERS_PER_METER = 100.0f;
const QString AVATAR_SETTINGS_GROUP_NAME { "Avatar" }; const QString AVATAR_SETTINGS_GROUP_NAME { "Avatar" };
static const QString USER_RECENTER_MODEL_FORCE_SIT = QStringLiteral("ForceSit"); static const QString ALLOW_AVATAR_STANDING_ALWAYS = QStringLiteral("Always");
static const QString USER_RECENTER_MODEL_FORCE_STAND = QStringLiteral("ForceStand"); static const QString ALLOW_AVATAR_STANDING_WHEN_USER_IS_STANDING = QStringLiteral("UserStanding");
static const QString USER_RECENTER_MODEL_AUTO = QStringLiteral("Auto");
static const QString USER_RECENTER_MODEL_DISABLE_HMD_LEAN = QStringLiteral("DisableHMDLean");
const QString HEAD_BLEND_DIRECTIONAL_ALPHA_NAME = "lookAroundAlpha"; const QString HEAD_BLEND_DIRECTIONAL_ALPHA_NAME = "lookAroundAlpha";
const QString HEAD_BLEND_LINEAR_ALPHA_NAME = "lookBlendAlpha"; 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 QString POINT_REF_JOINT_NAME = "RightShoulder";
const float POINT_ALPHA_BLENDING = 1.0f; const float POINT_ALPHA_BLENDING = 1.0f;
MyAvatar::SitStandModelType stringToUserRecenterModel(const QString& str) { const std::array<QString, static_cast<uint>(MyAvatar::AllowAvatarStandingPreference::Count)>
if (str == USER_RECENTER_MODEL_FORCE_SIT) { MyAvatar::allowAvatarStandingPreferenceStrings = {
return MyAvatar::ForceSit; QStringLiteral("WhenUserIsStanding"),
} else if (str == USER_RECENTER_MODEL_FORCE_STAND) { QStringLiteral("Always")
return MyAvatar::ForceStand; };
} else if (str == USER_RECENTER_MODEL_DISABLE_HMD_LEAN) {
return MyAvatar::DisableHMDLean; const std::array<QString, static_cast<uint>(MyAvatar::AllowAvatarLeaningPreference::Count)>
} else { MyAvatar::allowAvatarLeaningPreferenceStrings = {
return MyAvatar::Auto; QStringLiteral("WhenUserIsStanding"),
QStringLiteral("Always"),
QStringLiteral("Never"),
QStringLiteral("AlwaysNoRecenter")
};
MyAvatar::AllowAvatarStandingPreference stringToAllowAvatarStandingPreference(const QString& str) {
for (uint stringIndex = 0; stringIndex < static_cast<uint>(MyAvatar::AllowAvatarStandingPreference::Count); stringIndex++) {
if (MyAvatar::allowAvatarStandingPreferenceStrings[stringIndex] == str) {
return static_cast<MyAvatar::AllowAvatarStandingPreference>(stringIndex);
} }
}
return MyAvatar::AllowAvatarStandingPreference::Default;
} }
QString userRecenterModelToString(MyAvatar::SitStandModelType model) { MyAvatar::AllowAvatarLeaningPreference stringToAllowAvatarLeaningPreference(const QString& str) {
switch (model) { for (uint stringIndex = 0; stringIndex < static_cast<uint>(MyAvatar::AllowAvatarLeaningPreference::Count); stringIndex++) {
case MyAvatar::ForceSit: if (MyAvatar::allowAvatarLeaningPreferenceStrings[stringIndex] == str) {
return USER_RECENTER_MODEL_FORCE_SIT; return static_cast<MyAvatar::AllowAvatarLeaningPreference>(stringIndex);
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;
} }
}
return MyAvatar::AllowAvatarLeaningPreference::Default;
} }
static const QStringList TRIGGER_REACTION_NAMES = { static const QStringList TRIGGER_REACTION_NAMES = {
@ -166,7 +172,7 @@ MyAvatar::MyAvatar(QThread* thread) :
_scriptedMotorFrame(SCRIPTED_MOTOR_CAMERA_FRAME), _scriptedMotorFrame(SCRIPTED_MOTOR_CAMERA_FRAME),
_scriptedMotorMode(SCRIPTED_MOTOR_SIMPLE_MODE), _scriptedMotorMode(SCRIPTED_MOTOR_SIMPLE_MODE),
_motionBehaviors(AVATAR_MOTION_DEFAULTS), _motionBehaviors(AVATAR_MOTION_DEFAULTS),
_characterController(std::shared_ptr<MyAvatar>(this)), _characterController(std::shared_ptr<MyAvatar>(this), _follow._timeRemaining),
_eyeContactTarget(LEFT_EYE), _eyeContactTarget(LEFT_EYE),
_realWorldFieldOfView("realWorldFieldOfView", _realWorldFieldOfView("realWorldFieldOfView",
DEFAULT_REAL_WORLD_FIELD_OF_VIEW_DEGREES), DEFAULT_REAL_WORLD_FIELD_OF_VIEW_DEGREES),
@ -214,8 +220,12 @@ MyAvatar::MyAvatar(QThread* thread) :
_analogWalkSpeedSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "analogWalkSpeed", _analogWalkSpeed.get()), _analogWalkSpeedSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "analogWalkSpeed", _analogWalkSpeed.get()),
_analogPlusWalkSpeedSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "analogPlusWalkSpeed", _analogPlusWalkSpeed.get()), _analogPlusWalkSpeedSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "analogPlusWalkSpeed", _analogPlusWalkSpeed.get()),
_controlSchemeIndexSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "controlSchemeIndex", _controlSchemeIndex), _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<uint>(
AllowAvatarStandingPreference::Default)]),
_allowAvatarLeaningPreferenceSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "allowAvatarLeaningPreference",
allowAvatarLeaningPreferenceStrings[static_cast<uint>(
AllowAvatarLeaningPreference::Default)]) {
_clientTraitsHandler.reset(new ClientTraitsHandler(this)); _clientTraitsHandler.reset(new ClientTraitsHandler(this));
// give the pointer to our head to inherited _headData variable from AvatarData // give the pointer to our head to inherited _headData variable from AvatarData
@ -487,14 +497,17 @@ void MyAvatar::resetSensorsAndBody() {
reset(true, false, true); 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()) { if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "centerBody"); QMetaObject::invokeMethod(this, "centerBody");
return; return;
} }
// derive the desired body orientation from the current hmd orientation, before the sensor reset. // 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 // transform this body into world space
auto worldBodyMatrix = _sensorToWorldMatrix * newBodySensorMatrix; auto worldBodyMatrix = _sensorToWorldMatrix * newBodySensorMatrix;
@ -571,13 +584,13 @@ 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) { void MyAvatar::updateSitStandState(float newHeightReading, float dt) {
const float STANDING_HEIGHT_MULTIPLE = 1.2f; const float STANDING_HEIGHT_MULTIPLE = 1.2f;
const float SITTING_HEIGHT_MULTIPLE = 0.833f; const float SITTING_HEIGHT_MULTIPLE = 0.833f;
const float SITTING_TIMEOUT = 4.0f; // 4 seconds const float SITTING_TIMEOUT = 4.0f; // 4 seconds
const float STANDING_TIMEOUT = 0.3333f; // 1/3 second const float STANDING_TIMEOUT = 0.3333f; // 1/3 second
const float SITTING_UPPER_BOUND = 1.52f; const float SITTING_UPPER_BOUND = 1.52f;
if (!getIsSitStandStateLocked()) {
if (!getIsAway() && getControllerPoseInAvatarFrame(controller::Action::HEAD).isValid()) { if (!getIsAway() && getControllerPoseInAvatarFrame(controller::Action::HEAD).isValid()) {
if (getIsInSittingState()) { if (getIsInSittingState()) {
if (newHeightReading > (STANDING_HEIGHT_MULTIPLE * _tippingPoint)) { if (newHeightReading > (STANDING_HEIGHT_MULTIPLE * _tippingPoint)) {
@ -629,7 +642,6 @@ void MyAvatar::updateSitStandState(float newHeightReading, float dt) {
_tippingPoint = _userHeight.get(); _tippingPoint = _userHeight.get();
setIsInSittingState(false); setIsInSittingState(false);
} }
}
} }
void MyAvatar::update(float deltaTime) { void MyAvatar::update(float deltaTime) {
@ -692,9 +704,9 @@ void MyAvatar::update(float deltaTime) {
} }
float angleSpine2 = glm::dot(upSpine2, glm::vec3(0.0f, 1.0f, 0.0f)); float angleSpine2 = glm::dot(upSpine2, glm::vec3(0.0f, 1.0f, 0.0f));
if (getControllerPoseInAvatarFrame(controller::Action::HEAD).getTranslation().y < (headDefaultPositionAvatarSpace.y - SQUAT_THRESHOLD) && if (getHMDCrouchRecenterEnabled() &&
(angleSpine2 > COSINE_THIRTY_DEGREES) && getControllerPoseInAvatarFrame(controller::Action::HEAD).getTranslation().y < (headDefaultPositionAvatarSpace.y - SQUAT_THRESHOLD) &&
(getUserRecenterModel() != MyAvatar::SitStandModelType::ForceStand)) { (angleSpine2 > COSINE_THIRTY_DEGREES)) {
_squatTimer += deltaTime; _squatTimer += deltaTime;
if (_squatTimer > SQUATTY_TIMEOUT) { if (_squatTimer > SQUATTY_TIMEOUT) {
@ -832,7 +844,7 @@ void MyAvatar::recalculateChildCauterization() const {
_cauterizationNeedsUpdate = true; _cauterizationNeedsUpdate = true;
} }
bool MyAvatar::isFollowActive(FollowHelper::FollowType followType) const { bool MyAvatar::isFollowActive(CharacterController::FollowType followType) const {
return _follow.isActive(followType); return _follow.isActive(followType);
} }
@ -1277,6 +1289,10 @@ void MyAvatar::resizeAvatarEntitySettingHandles(uint32_t maxIndex) {
void MyAvatar::saveData() { void MyAvatar::saveData() {
_dominantHandSetting.set(getDominantHand()); _dominantHandSetting.set(getDominantHand());
_allowAvatarStandingPreferenceSetting.set(
allowAvatarStandingPreferenceStrings[static_cast<uint>(getAllowAvatarStandingPreference())]);
_allowAvatarLeaningPreferenceSetting.set(
allowAvatarLeaningPreferenceStrings[static_cast<uint>(getAllowAvatarLeaningPreference())]);
_strafeEnabledSetting.set(getStrafeEnabled()); _strafeEnabledSetting.set(getStrafeEnabled());
_hmdAvatarAlignmentTypeSetting.set(getHmdAvatarAlignmentType()); _hmdAvatarAlignmentTypeSetting.set(getHmdAvatarAlignmentType());
_headPitchSetting.set(getHead()->getBasePitch()); _headPitchSetting.set(getHead()->getBasePitch());
@ -1311,7 +1327,10 @@ void MyAvatar::saveData() {
_analogWalkSpeedSetting.set(getAnalogWalkSpeed()); _analogWalkSpeedSetting.set(getAnalogWalkSpeed());
_analogPlusWalkSpeedSetting.set(getAnalogPlusWalkSpeed()); _analogPlusWalkSpeedSetting.set(getAnalogPlusWalkSpeed());
_controlSchemeIndexSetting.set(getControlSchemeIndex()); _controlSchemeIndexSetting.set(getControlSchemeIndex());
_userRecenterModelSetting.set(userRecenterModelToString(getUserRecenterModel())); _allowAvatarStandingPreferenceSetting.set(
allowAvatarStandingPreferenceStrings[static_cast<uint>(getAllowAvatarStandingPreference())]);
_allowAvatarLeaningPreferenceSetting.set(
allowAvatarLeaningPreferenceStrings[static_cast<uint>(getAllowAvatarLeaningPreference())]);
auto hmdInterface = DependencyManager::get<HMDScriptingInterface>(); auto hmdInterface = DependencyManager::get<HMDScriptingInterface>();
saveAvatarEntityDataToSettings(); saveAvatarEntityDataToSettings();
@ -2004,7 +2023,10 @@ void MyAvatar::loadData() {
setUserHeight(_userHeightSetting.get(DEFAULT_AVATAR_HEIGHT)); setUserHeight(_userHeightSetting.get(DEFAULT_AVATAR_HEIGHT));
setTargetScale(_scaleSetting.get()); setTargetScale(_scaleSetting.get());
setUserRecenterModel(stringToUserRecenterModel(_userRecenterModelSetting.get(USER_RECENTER_MODEL_AUTO))); setAllowAvatarStandingPreference(stringToAllowAvatarStandingPreference(_allowAvatarStandingPreferenceSetting.get(
allowAvatarStandingPreferenceStrings[static_cast<uint>(AllowAvatarStandingPreference::Default)])));
setAllowAvatarLeaningPreference(stringToAllowAvatarLeaningPreference(_allowAvatarLeaningPreferenceSetting.get(
allowAvatarLeaningPreferenceStrings[static_cast<uint>(AllowAvatarLeaningPreference::Default)])));
setEnableMeshVisible(Menu::getInstance()->isOptionChecked(MenuOption::MeshVisible)); setEnableMeshVisible(Menu::getInstance()->isOptionChecked(MenuOption::MeshVisible));
_follow.setToggleHipsFollowing (Menu::getInstance()->isOptionChecked(MenuOption::ToggleHipsFollowing)); _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() { void MyAvatar::updateMotors() {
_characterController.clearMotors(); _characterController.clearMotors();
glm::quat motorRotation;
const float FLYING_MOTOR_TIMESCALE = 0.05f; const float FLYING_MOTOR_TIMESCALE = 0.05f;
const float WALKING_MOTOR_TIMESCALE = 0.2f; const float WALKING_MOTOR_TIMESCALE = 0.2f;
@ -2693,35 +2708,17 @@ void MyAvatar::updateMotors() {
} }
if (_motionBehaviors & AVATAR_MOTION_ACTION_MOTOR_ENABLED) { 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) { if (_isPushing || _isBraking || !_isBeingPushed) {
_characterController.addMotor(_actionMotorVelocity, motorRotation, horizontalMotorTimescale, verticalMotorTimescale); _characterController.addMotor(_actionMotorVelocity, Quaternions::IDENTITY, horizontalMotorTimescale,
verticalMotorTimescale);
} else { } else {
// _isBeingPushed must be true --> disable action motor by giving it a long timescale, // _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 // 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) { if (_motionBehaviors & AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED) {
glm::quat motorRotation;
if (_scriptedMotorFrame == SCRIPTED_MOTOR_CAMERA_FRAME) { if (_scriptedMotorFrame == SCRIPTED_MOTOR_CAMERA_FRAME) {
motorRotation = getMyHead()->getHeadOrientation() * glm::angleAxis(PI, Vectors::UNIT_Y); motorRotation = getMyHead()->getHeadOrientation() * glm::angleAxis(PI, Vectors::UNIT_Y);
} else if (_scriptedMotorFrame == SCRIPTED_MOTOR_AVATAR_FRAME) { } 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(); CharacterController::State state = _characterController.getState();
// compute action input // compute action input
// Determine if we're head or controller relative... // Determine if we're head or controller relative...
glm::vec3 forward, right; glm::vec3 forward;
glm::vec3 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()) { if (qApp->isHMDMode()) {
auto handRotation = getOffHandRotation(); forward = -IDENTITY_FORWARD;
glm::vec3 controllerForward(0.0f, 1.0f, 0.0f); right = -IDENTITY_RIGHT;
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;
}
} else { } else {
forward = IDENTITY_FORWARD; forward = IDENTITY_FORWARD;
right = IDENTITY_RIGHT; 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;
}
}
glm::vec3 direction = scaleMotorSpeed(forward, right); glm::vec3 direction = scaleMotorSpeed(forward, right);
if (state == CharacterController::State::Hover || if (vectorsAreInAvatarFrame) {
_characterController.computeCollisionMask() == BULLET_COLLISION_MASK_COLLISIONLESS) { direction = getWorldOrientation() * direction;
glm::vec3 up = (getDriveKey(TRANSLATE_Y)) * IDENTITY_UP; }
if (hoveringOrCollisionless) {
glm::vec3 up = getDriveKey(TRANSLATE_Y) * IDENTITY_UP;
direction += up; direction += up;
} }
@ -4724,7 +4782,9 @@ void MyAvatar::triggerRotationRecenter() {
} }
// old school meat hook style // 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::vec3 headPosition(0.0f, _userHeight.get(), 0.0f);
glm::quat headOrientation; glm::quat headOrientation;
auto headPose = getControllerPoseInSensorFrame(controller::Action::HEAD); auto headPose = getControllerPoseInSensorFrame(controller::Action::HEAD);
@ -4760,8 +4820,30 @@ glm::mat4 MyAvatar::deriveBodyFromHMDSensor() const {
float invSensorToWorldScale = getUserEyeHeight() / getEyeHeight(); float invSensorToWorldScale = getUserEyeHeight() / getEyeHeight();
glm::vec3 bodyPos = headPosition + invSensorToWorldScale * (headToNeck + neckToRoot); 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 { glm::mat4 MyAvatar::getSpine2RotationRigSpace() const {
@ -5159,16 +5241,19 @@ bool MyAvatar::getIsInWalkingState() const {
return _isInWalkingState; return _isInWalkingState;
} }
// Determine if the user is sitting in the real world.
bool MyAvatar::getIsInSittingState() const { bool MyAvatar::getIsInSittingState() const {
return _isInSittingState.get(); return _isInSittingState.get();
} }
MyAvatar::SitStandModelType MyAvatar::getUserRecenterModel() const { // Get the user preference of when MyAvatar may stand.
return _userRecenterModel.get(); MyAvatar::AllowAvatarStandingPreference MyAvatar::getAllowAvatarStandingPreference() const {
return _allowAvatarStandingPreference.get();
} }
bool MyAvatar::getIsSitStandStateLocked() const { // Get the user preference of when MyAvatar may lean.
return _lockSitStandState.get(); MyAvatar::AllowAvatarLeaningPreference MyAvatar::getAllowAvatarLeaningPreference() const {
return _allowAvatarLeaningPreference.get();
} }
float MyAvatar::getWalkSpeed() const { float MyAvatar::getWalkSpeed() const {
@ -5221,59 +5306,29 @@ void MyAvatar::setIsInWalkingState(bool isWalking) {
_isInWalkingState = isWalking; _isInWalkingState = isWalking;
} }
// Specify whether the user is sitting or standing in the real world.
void MyAvatar::setIsInSittingState(bool isSitting) { void MyAvatar::setIsInSittingState(bool isSitting) {
_sitStandStateTimer = 0.0f; _sitStandStateTimer = 0.0f;
_squatTimer = 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. // on reset height we need the count to be more than one in case the user sits and stands up quickly.
_isInSittingState.set(isSitting); _isInSittingState.set(isSitting);
setResetMode(true); setResetMode(true);
if (isSitting) { setCenterOfGravityModelEnabled(!isSitting);
setCenterOfGravityModelEnabled(false);
} else {
setCenterOfGravityModelEnabled(true);
}
setSitStandStateChange(true); 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); // Set the correct vertical position for the avatar body relative to the HMD,
// according to the newly-selected avatar standing preference.
switch (modelName) { centerBody(false);
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;
}
} }
void MyAvatar::setIsSitStandStateLocked(bool isLocked) { // Set the user preference of when the avatar may lean.
_lockSitStandState.set(isLocked); void MyAvatar::setAllowAvatarLeaningPreference(const MyAvatar::AllowAvatarLeaningPreference preference) {
_sitStandStateTimer = 0.0f; _allowAvatarLeaningPreference.set(preference);
_squatTimer = 0.0f;
_averageUserHeightSensorSpace = _userHeight.get();
_tippingPoint = _userHeight.get();
if (!isLocked) {
// always start the auto transition mode in standing state.
setIsInSittingState(false);
}
} }
void MyAvatar::setWalkSpeed(float value) { void MyAvatar::setWalkSpeed(float value) {
@ -5402,10 +5457,12 @@ float MyAvatar::getAnalogPlusSprintSpeed() const {
return _analogPlusSprintSpeed.get(); return _analogPlusSprintSpeed.get();
} }
// Indicate whether the user's real-world sit/stand state has changed or not.
void MyAvatar::setSitStandStateChange(bool stateChanged) { void MyAvatar::setSitStandStateChange(bool stateChanged) {
_sitStandStateChange = stateChanged; _sitStandStateChange = stateChanged;
} }
// Determine if the user's real-world sit/stand state has changed.
float MyAvatar::getSitStandStateChange() const { float MyAvatar::getSitStandStateChange() const {
return _sitStandStateChange; return _sitStandStateChange;
} }
@ -5499,65 +5556,82 @@ MyAvatar::FollowHelper::FollowHelper() {
} }
void MyAvatar::FollowHelper::deactivate() { void MyAvatar::FollowHelper::deactivate() {
for (int i = 0; i < NumFollowTypes; i++) { for (uint i = 0, e = static_cast<uint>(CharacterController::FollowType::Count); i < e; ++i) {
deactivate((FollowType)i); deactivate((CharacterController::FollowType)i);
} }
} }
void MyAvatar::FollowHelper::deactivate(FollowType type) { void MyAvatar::FollowHelper::deactivate(CharacterController::FollowType type) {
assert(type >= 0 && type < NumFollowTypes); assert(static_cast<int>(type) >= 0 && type < CharacterController::FollowType::Count);
_timeRemaining[(int)type] = 0.0f; _timeRemaining[(int)type] = 0.0f;
} }
void MyAvatar::FollowHelper::activate(FollowType type) { // snapFollow: true to snap immediately to the desired transform with regard to 'type',
assert(type >= 0 && type < NumFollowTypes); // 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<int>(type) >= 0 && type < CharacterController::FollowType::Count);
// TODO: Perhaps, the follow time should be proportional to the displacement. // 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 { bool MyAvatar::FollowHelper::isActive(CharacterController::FollowType type) const {
assert(type >= 0 && type < NumFollowTypes); assert(static_cast<int>(type) >= 0 && type < CharacterController::FollowType::Count);
return _timeRemaining[(int)type] > 0.0f; return _timeRemaining[(int)type] > 0.0f;
} }
bool MyAvatar::FollowHelper::isActive() const { bool MyAvatar::FollowHelper::isActive() const {
for (int i = 0; i < NumFollowTypes; i++) { for (uint i = 0, e = static_cast<uint>(CharacterController::FollowType::Count); i < e; ++i) {
if (isActive((FollowType)i)) { if (isActive((CharacterController::FollowType)i)) {
return true; return true;
} }
} }
return false; 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];
}
}
return max;
}
void MyAvatar::FollowHelper::decrementTimeRemaining(float dt) { void MyAvatar::FollowHelper::decrementTimeRemaining(float dt) {
for (int i = 0; i < NumFollowTypes; i++) { for (uint i = 0, e = static_cast<uint>(CharacterController::FollowType::Count); i < e; ++i) {
if (_timeRemaining[i] == CharacterController::FOLLOW_TIME_IMMEDIATE_SNAP) {
_timeRemaining[i] = 0.f;
}
else {
_timeRemaining[i] -= dt; _timeRemaining[i] -= dt;
} }
}
} }
bool MyAvatar::FollowHelper::shouldActivateRotation(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const { // 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;
}
const float FOLLOW_ROTATION_THRESHOLD = cosf(myAvatar.getRotationThreshold()); const float FOLLOW_ROTATION_THRESHOLD = cosf(myAvatar.getRotationThreshold());
glm::vec2 bodyFacing = getFacingDir2D(currentBodyMatrix); glm::vec2 bodyFacing = getFacingDir2D(currentBodyMatrix);
return glm::dot(-myAvatar.getHeadControllerFacingMovingAverage(), bodyFacing) < FOLLOW_ROTATION_THRESHOLD; 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 { 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. // -z axis of currentBodyMatrix in world space.
glm::vec3 forward = glm::normalize(glm::vec3(-currentBodyMatrix[0][2], -currentBodyMatrix[1][2], -currentBodyMatrix[2][2])); glm::vec3 forward = glm::normalize(glm::vec3(-currentBodyMatrix[0][2], -currentBodyMatrix[1][2], -currentBodyMatrix[2][2]));
// x axis of currentBodyMatrix in world space. // 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 right = glm::normalize(glm::vec3(currentBodyMatrix[0][0], currentBodyMatrix[1][0], currentBodyMatrix[2][0]));
glm::vec3 offset = extractTranslation(desiredBodyMatrix) - extractTranslation(currentBodyMatrix); glm::vec3 offset = extractTranslation(desiredBodyMatrix) - extractTranslation(currentBodyMatrix);
controller::Pose currentHeadPose = myAvatar.getControllerPoseInAvatarFrame(controller::Action::HEAD);
float forwardLeanAmount = glm::dot(forward, offset); float forwardLeanAmount = glm::dot(forward, offset);
float lateralLeanAmount = glm::dot(right, 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; const float MAX_BACKWARD_LEAN = 0.1f;
bool stepDetected = false; bool stepDetected = false;
if (myAvatar.getIsInSittingState()) { if (forwardLeanAmount > MAX_FORWARD_LEAN) {
if (!withinBaseOfSupport(currentHeadPose)) {
stepDetected = true;
}
} else if (forwardLeanAmount > 0 && forwardLeanAmount > MAX_FORWARD_LEAN) {
stepDetected = true; stepDetected = true;
} else if (forwardLeanAmount < 0 && forwardLeanAmount < -MAX_BACKWARD_LEAN) { } else if (forwardLeanAmount < 0 && forwardLeanAmount < -MAX_BACKWARD_LEAN) {
stepDetected = true; stepDetected = true;
@ -5583,20 +5653,25 @@ bool MyAvatar::FollowHelper::shouldActivateHorizontal(const MyAvatar& myAvatar,
bool MyAvatar::FollowHelper::shouldActivateHorizontalCG(MyAvatar& myAvatar) const { bool MyAvatar::FollowHelper::shouldActivateHorizontalCG(MyAvatar& myAvatar) const {
// get the current readings 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)) {
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 currentLeftHandPose = myAvatar.getControllerPoseInAvatarFrame(controller::Action::LEFT_HAND);
controller::Pose currentRightHandPose = myAvatar.getControllerPoseInAvatarFrame(controller::Action::RIGHT_HAND); controller::Pose currentRightHandPose = myAvatar.getControllerPoseInAvatarFrame(controller::Action::RIGHT_HAND);
controller::Pose currentHeadSensorPose = myAvatar.getControllerPoseInSensorFrame(controller::Action::HEAD); controller::Pose currentHeadSensorPose = myAvatar.getControllerPoseInSensorFrame(controller::Action::HEAD);
bool stepDetected = false; if (headAngularVelocityBelowThreshold(currentHeadPose) &&
float myScale = myAvatar.getAvatarScale();
if (myAvatar.getIsInWalkingState()) {
stepDetected = true;
} else {
if (!withinBaseOfSupport(currentHeadPose) &&
headAngularVelocityBelowThreshold(currentHeadPose) &&
isWithinThresholdHeightMode(currentHeadSensorPose, myAvatar.getCurrentStandingHeight(), myScale) && isWithinThresholdHeightMode(currentHeadSensorPose, myAvatar.getCurrentStandingHeight(), myScale) &&
handDirectionMatchesHeadDirection(currentLeftHandPose, currentRightHandPose, currentHeadPose) && handDirectionMatchesHeadDirection(currentLeftHandPose, currentRightHandPose, currentHeadPose) &&
handAngularVelocityBelowThreshold(currentLeftHandPose, currentRightHandPose) && handAngularVelocityBelowThreshold(currentLeftHandPose, currentRightHandPose) &&
@ -5604,29 +5679,34 @@ bool MyAvatar::FollowHelper::shouldActivateHorizontalCG(MyAvatar& myAvatar) cons
isHeadLevel(currentHeadPose, myAvatar.getAverageHeadRotation())) { isHeadLevel(currentHeadPose, myAvatar.getAverageHeadRotation())) {
// a step is detected // a step is detected
stepDetected = true; stepDetected = true;
if (glm::length(currentHeadPose.velocity) > DEFAULT_AVATAR_WALK_SPEED_THRESHOLD) {
myAvatar.setIsInWalkingState(true);
} }
} else { }
}
if (!stepDetected) {
glm::vec3 defaultHipsPosition = myAvatar.getAbsoluteDefaultJointTranslationInObjectFrame(myAvatar.getJointIndex("Hips")); glm::vec3 defaultHipsPosition = myAvatar.getAbsoluteDefaultJointTranslationInObjectFrame(myAvatar.getJointIndex("Hips"));
glm::vec3 defaultHeadPosition = myAvatar.getAbsoluteDefaultJointTranslationInObjectFrame(myAvatar.getJointIndex("Head")); glm::vec3 defaultHeadPosition = myAvatar.getAbsoluteDefaultJointTranslationInObjectFrame(myAvatar.getJointIndex("Head"));
glm::vec3 currentHeadPosition = currentHeadPose.getTranslation(); glm::vec3 currentHeadPosition = currentHeadPose.getTranslation();
float anatomicalHeadToHipsDistance = glm::length(defaultHeadPosition - defaultHipsPosition); float anatomicalHeadToHipsDistance = glm::length(defaultHeadPosition - defaultHipsPosition);
if (!isActive(Horizontal) && if (!isActive(CharacterController::FollowType::Horizontal) && (!isActive(CharacterController::FollowType::Vertical)) &&
(!isActive(Vertical)) &&
(glm::length(currentHeadPosition - defaultHipsPosition) > (anatomicalHeadToHipsDistance + (DEFAULT_AVATAR_SPINE_STRETCH_LIMIT * anatomicalHeadToHipsDistance)))) { (glm::length(currentHeadPosition - defaultHipsPosition) > (anatomicalHeadToHipsDistance + (DEFAULT_AVATAR_SPINE_STRETCH_LIMIT * anatomicalHeadToHipsDistance)))) {
myAvatar.setResetMode(true); myAvatar.setResetMode(true);
stepDetected = true; stepDetected = true;
}
}
if (stepDetected) {
if (glm::length(currentHeadPose.velocity) > DEFAULT_AVATAR_WALK_SPEED_THRESHOLD) { if (glm::length(currentHeadPose.velocity) > DEFAULT_AVATAR_WALK_SPEED_THRESHOLD) {
myAvatar.setIsInWalkingState(true); myAvatar.setIsInWalkingState(true);
} }
} }
}
}
return stepDetected; 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_TOP = 2.0f;
const float CYLINDER_BOTTOM = -1.5f; const float CYLINDER_BOTTOM = -1.5f;
const float SITTING_BOTTOM = -0.02f; const float SITTING_BOTTOM = -0.02f;
@ -5638,9 +5718,6 @@ bool MyAvatar::FollowHelper::shouldActivateVertical(const MyAvatar& myAvatar, co
returnValue = true; returnValue = true;
} else { } else {
if (myAvatar.getIsInSittingState()) { if (myAvatar.getIsInSittingState()) {
if (myAvatar.getIsSitStandStateLocked()) {
returnValue = (offset.y > CYLINDER_TOP);
}
if (offset.y < SITTING_BOTTOM) { if (offset.y < SITTING_BOTTOM) {
// we recenter more easily when in sitting state. // we recenter more easily when in sitting state.
returnValue = true; returnValue = true;
@ -5657,51 +5734,80 @@ bool MyAvatar::FollowHelper::shouldActivateVertical(const MyAvatar& myAvatar, co
return returnValue; return returnValue;
} }
void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar,
const glm::mat4& currentBodyMatrix, bool hasDriveInput) { const glm::mat4& desiredBodyMatrix,
const glm::mat4& currentBodyMatrix,
bool hasDriveInput) {
const bool feetAreTracked = myAvatar.areFeetTracked();
if (myAvatar.getHMDLeanRecenterEnabled() && if (myAvatar.getHMDLeanRecenterEnabled()) {
qApp->getCamera().getMode() != CAMERA_MODE_MIRROR) {
if (!isActive(Rotation) && (shouldActivateRotation(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) { // Rotation recenter
activate(Rotation);
{
bool snapFollow = false;
if (!isActive(CharacterController::FollowType::Rotation) &&
(shouldActivateRotation(myAvatar, desiredBodyMatrix, currentBodyMatrix, snapFollow) || hasDriveInput)) {
activate(CharacterController::FollowType::Rotation, snapFollow);
myAvatar.setHeadControllerFacingMovingAverage(myAvatar.getHeadControllerFacing()); myAvatar.setHeadControllerFacingMovingAverage(myAvatar.getHeadControllerFacing());
} }
}
// Horizontal and rotation recenter
if ((feetAreTracked || getForceActivateHorizontal()) && !isActive(CharacterController::FollowType::Horizontal)) {
activate(CharacterController::FollowType::Horizontal, feetAreTracked);
setForceActivateHorizontal(false);
} else {
if ((myAvatar.getAllowAvatarLeaningPreference() != MyAvatar::AllowAvatarLeaningPreference::AlwaysNoRecenter) &&
qApp->getCamera().getMode() != CAMERA_MODE_MIRROR) {
if (myAvatar.getCenterOfGravityModelEnabled()) { if (myAvatar.getCenterOfGravityModelEnabled()) {
if (!isActive(Horizontal) && (shouldActivateHorizontalCG(myAvatar) || hasDriveInput)) { if (!isActive(CharacterController::FollowType::Horizontal) && (shouldActivateHorizontalCG(myAvatar) || hasDriveInput)) {
activate(Horizontal); activate(CharacterController::FollowType::Horizontal, false);
if (myAvatar.getEnableStepResetRotation()) { if (myAvatar.getEnableStepResetRotation()) {
activate(Rotation); activate(CharacterController::FollowType::Rotation, false);
myAvatar.setHeadControllerFacingMovingAverage(myAvatar.getHeadControllerFacing()); myAvatar.setHeadControllerFacingMovingAverage(myAvatar.getHeadControllerFacing());
} }
} }
} else { } else {
// center of gravity model is not enabled // center of gravity model is not enabled
if (!isActive(Horizontal) && (shouldActivateHorizontal(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) { if (!isActive(CharacterController::FollowType::Horizontal) &&
activate(Horizontal); (shouldActivateHorizontal(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) {
activate(CharacterController::FollowType::Horizontal, false);
if (myAvatar.getEnableStepResetRotation() && !myAvatar.getIsInSittingState()) { if (myAvatar.getEnableStepResetRotation() && !myAvatar.getIsInSittingState()) {
activate(Rotation); activate(CharacterController::FollowType::Rotation, false);
myAvatar.setHeadControllerFacingMovingAverage(myAvatar.getHeadControllerFacing()); myAvatar.setHeadControllerFacingMovingAverage(myAvatar.getHeadControllerFacing());
} }
} }
} }
if (!isActive(Vertical) && (shouldActivateVertical(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) { }
activate(Vertical); }
// 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) { if (_squatDetected) {
_squatDetected = false; _squatDetected = false;
} }
} }
}
} else { } else {
if (!isActive(Rotation) && getForceActivateRotation()) { // Forced activations can be requested by MyAvatar::triggerVerticalRecenter, callable from scripts.
activate(Rotation);
if (!isActive(CharacterController::FollowType::Rotation) && getForceActivateRotation()) {
activate(CharacterController::FollowType::Rotation, true);
myAvatar.setHeadControllerFacingMovingAverage(myAvatar.getHeadControllerFacing()); myAvatar.setHeadControllerFacingMovingAverage(myAvatar.getHeadControllerFacing());
setForceActivateRotation(false); setForceActivateRotation(false);
} }
if (!isActive(Horizontal) && getForceActivateHorizontal()) { if (!isActive(CharacterController::FollowType::Horizontal) && getForceActivateHorizontal()) {
activate(Horizontal); activate(CharacterController::FollowType::Horizontal, true);
setForceActivateHorizontal(false); setForceActivateHorizontal(false);
} }
if (!isActive(Vertical) && getForceActivateVertical()) { if (!isActive(CharacterController::FollowType::Vertical) && getForceActivateVertical()) {
activate(Vertical); activate(CharacterController::FollowType::Vertical, true);
setForceActivateVertical(false); setForceActivateVertical(false);
} }
} }
@ -5721,21 +5827,21 @@ void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat
// remove scale present from sensorToWorldMatrix // remove scale present from sensorToWorldMatrix
followWorldPose.scale() = glm::vec3(1.0f); followWorldPose.scale() = glm::vec3(1.0f);
if (isActive(Rotation)) { if (isActive(CharacterController::FollowType::Rotation)) {
//use the hmd reading for the hips follow //use the hmd reading for the hips follow
followWorldPose.rot() = glmExtractRotation(desiredWorldMatrix); followWorldPose.rot() = glmExtractRotation(desiredWorldMatrix);
} }
if (isActive(Horizontal)) { if (isActive(CharacterController::FollowType::Horizontal)) {
glm::vec3 desiredTranslation = extractTranslation(desiredWorldMatrix); glm::vec3 desiredTranslation = extractTranslation(desiredWorldMatrix);
followWorldPose.trans().x = desiredTranslation.x; followWorldPose.trans().x = desiredTranslation.x;
followWorldPose.trans().z = desiredTranslation.z; followWorldPose.trans().z = desiredTranslation.z;
} }
if (isActive(Vertical)) { if (isActive(CharacterController::FollowType::Vertical)) {
glm::vec3 desiredTranslation = extractTranslation(desiredWorldMatrix); glm::vec3 desiredTranslation = extractTranslation(desiredWorldMatrix);
followWorldPose.trans().y = desiredTranslation.y; 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) { 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), glm::mat4 newBodyMat = createMatFromQuatAndPos(sensorAngularDisplacement * glmExtractRotation(currentBodyMatrix),
sensorLinearDisplacement + extractTranslation(currentBodyMatrix)); sensorLinearDisplacement + extractTranslation(currentBodyMatrix));
if (myAvatar.getHMDCrouchRecenterEnabled()) {
if (myAvatar.getSitStandStateChange()) { if (myAvatar.getSitStandStateChange()) {
myAvatar.setSitStandStateChange(false); myAvatar.setSitStandStateChange(false);
deactivate(Vertical); deactivate(CharacterController::FollowType::Vertical);
setTranslation(newBodyMat, extractTranslation(myAvatar.deriveBodyFromHMDSensor())); setTranslation(newBodyMat, extractTranslation(myAvatar.deriveBodyFromHMDSensor()));
} }
}
return newBodyMat; return newBodyMat;
} else { } else {
return currentBodyMatrix; return currentBodyMatrix;
@ -6127,7 +6237,7 @@ void MyAvatar::updateHoldActions(const AnimPose& prePhysicsPose, const AnimPose&
} }
bool MyAvatar::isRecenteringHorizontally() const { bool MyAvatar::isRecenteringHorizontally() const {
return _follow.isActive(FollowHelper::Horizontal); return _follow.isActive(CharacterController::FollowType::Horizontal);
} }
const MyHead* MyAvatar::getMyHead() const { const MyHead* MyAvatar::getMyHead() const {
@ -6583,7 +6693,7 @@ void MyAvatar::beginSit(const glm::vec3& position, const glm::quat& rotation) {
setHMDLeanRecenterEnabled(false); setHMDLeanRecenterEnabled(false);
// Disable movement // Disable movement
setSitDriveKeysStatus(false); setSitDriveKeysStatus(false);
centerBody(); centerBody(true);
int hipIndex = getJointIndex("Hips"); int hipIndex = getJointIndex("Hips");
clearPinOnJoint(hipIndex); clearPinOnJoint(hipIndex);
pinJoint(hipIndex, position, rotation); pinJoint(hipIndex, position, rotation);
@ -6601,7 +6711,7 @@ void MyAvatar::endSit(const glm::vec3& position, const glm::quat& rotation) {
_characterController.setSeated(false); _characterController.setSeated(false);
setCollisionsEnabled(true); setCollisionsEnabled(true);
setHMDLeanRecenterEnabled(true); setHMDLeanRecenterEnabled(true);
centerBody(); centerBody(false);
slamPosition(position); slamPosition(position);
setWorldOrientation(rotation); setWorldOrientation(rotation);
@ -6906,6 +7016,30 @@ bool MyAvatar::isJumping() {
_characterController.getState() == CharacterController::State::Takeoff) && !isFlying(); _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) { bool MyAvatar::setPointAt(const glm::vec3& pointAtTarget) {
if (QThread::currentThread() != thread()) { if (QThread::currentThread() != thread()) {
bool result = false; bool result = false;

View file

@ -282,16 +282,11 @@ class MyAvatar : public Avatar {
* <p><strong>Warning:</strong> Setting this value also sets the value of <code>analogPlusSprintSpeed</code> to twice * <p><strong>Warning:</strong> Setting this value also sets the value of <code>analogPlusSprintSpeed</code> to twice
* the value.</p> * the value.</p>
* @property {number} analogPlusSprintSpeed - The sprint (run) speed of your avatar for the "AnalogPlus" control scheme. * @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 - <code>true</code> if the user wearing the HMD is determined to be sitting;
* @property {number} isInSittingState - <code>true</code> if the user wearing the HMD is determined to be sitting * <code>false</code> if the user wearing the HMD is determined to be standing. This can affect whether the avatar
* (avatar leaning is disabled, recentering is enabled), <code>false</code> if the user wearing the HMD is * is allowed to stand, lean or recenter its footing, depending on user preferences.
* determined to be standing (avatar leaning is enabled, and avatar recenters if it leans too far). * The property value automatically updates as the user sits or stands. Setting the property value overrides the current
* If <code>userRecenterModel == 2</code> (i.e., "auto") the property value automatically updates as the user sits * sitting / standing state, which is updated when the user next sits or stands.
* or stands, unless <code>isSitStandStateLocked == true</code>. Setting the property value overrides the current
* sitting / standing state, which is updated when the user next sits or stands unless
* <code>isSitStandStateLocked == true</code>.
* @property {boolean} isSitStandStateLocked - <code>true</code> to lock the avatar sitting/standing state, i.e., use this
* to disable automatically changing state.
* @property {boolean} allowTeleporting - <code>true</code> if teleporting is enabled in the Interface settings, * @property {boolean} allowTeleporting - <code>true</code> if teleporting is enabled in the Interface settings,
* <code>false</code> if it isn't. <em>Read-only.</em> * <code>false</code> if it isn't. <em>Read-only.</em>
* *
@ -413,8 +408,6 @@ class MyAvatar : public Avatar {
Q_PROPERTY(float walkBackwardSpeed READ getWalkBackwardSpeed WRITE setWalkBackwardSpeed NOTIFY walkBackwardSpeedChanged); Q_PROPERTY(float walkBackwardSpeed READ getWalkBackwardSpeed WRITE setWalkBackwardSpeed NOTIFY walkBackwardSpeedChanged);
Q_PROPERTY(float sprintSpeed READ getSprintSpeed WRITE setSprintSpeed NOTIFY sprintSpeedChanged); Q_PROPERTY(float sprintSpeed READ getSprintSpeed WRITE setSprintSpeed NOTIFY sprintSpeedChanged);
Q_PROPERTY(bool isInSittingState READ getIsInSittingState WRITE setIsInSittingState); 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) Q_PROPERTY(bool allowTeleporting READ getAllowTeleporting)
const QString DOMINANT_LEFT_HAND = "left"; const QString DOMINANT_LEFT_HAND = "left";
@ -549,6 +542,31 @@ public:
}; };
Q_ENUM(SitStandModelType) 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<QString, (uint)AllowAvatarStandingPreference::Count> allowAvatarStandingPreferenceStrings;
static const std::array<QString, (uint)AllowAvatarLeaningPreference::Count> allowAvatarLeaningPreferenceStrings;
explicit MyAvatar(QThread* thread); explicit MyAvatar(QThread* thread);
virtual ~MyAvatar(); virtual ~MyAvatar();
@ -576,7 +594,7 @@ public:
* the HMD. * the HMD.
* @function MyAvatar.centerBody * @function MyAvatar.centerBody
*/ */
Q_INVOKABLE void centerBody(); // thread-safe Q_INVOKABLE void centerBody(const bool forceFollowYPos); // thread-safe
/**jsdoc /**jsdoc
@ -1417,7 +1435,6 @@ public:
controller::Pose getControllerPoseInSensorFrame(controller::Action action) const; controller::Pose getControllerPoseInSensorFrame(controller::Action action) const;
controller::Pose getControllerPoseInWorldFrame(controller::Action action) const; controller::Pose getControllerPoseInWorldFrame(controller::Action action) const;
controller::Pose getControllerPoseInAvatarFrame(controller::Action action) const; controller::Pose getControllerPoseInAvatarFrame(controller::Action action) const;
glm::quat getOffHandRotation() const;
bool hasDriveInput() const; bool hasDriveInput() const;
@ -1709,7 +1726,7 @@ public:
// derive avatar body position and orientation from the current HMD Sensor location. // derive avatar body position and orientation from the current HMD Sensor location.
// results are in sensor frame (-z forward) // results are in sensor frame (-z forward)
glm::mat4 deriveBodyFromHMDSensor() const; glm::mat4 deriveBodyFromHMDSensor(const bool forceFollowYPos = false) const;
glm::mat4 getSpine2RotationRigSpace() const; glm::mat4 getSpine2RotationRigSpace() const;
@ -1753,10 +1770,10 @@ public:
bool getIsInWalkingState() const; bool getIsInWalkingState() const;
void setIsInSittingState(bool isSitting); void setIsInSittingState(bool isSitting);
bool getIsInSittingState() const; bool getIsInSittingState() const;
void setUserRecenterModel(MyAvatar::SitStandModelType modelName); void setAllowAvatarStandingPreference(const AllowAvatarStandingPreference preference);
MyAvatar::SitStandModelType getUserRecenterModel() const; AllowAvatarStandingPreference getAllowAvatarStandingPreference() const;
void setIsSitStandStateLocked(bool isLocked); void setAllowAvatarLeaningPreference(const AllowAvatarLeaningPreference preference);
bool getIsSitStandStateLocked() const; AllowAvatarLeaningPreference getAllowAvatarLeaningPreference() const;
void setWalkSpeed(float value); void setWalkSpeed(float value);
float getWalkSpeed() const; float getWalkSpeed() const;
void setWalkBackwardSpeed(float value); void setWalkBackwardSpeed(float value);
@ -1989,6 +2006,10 @@ public:
glm::vec3 getLookAtPivotPoint(); glm::vec3 getLookAtPivotPoint();
glm::vec3 getCameraEyesPosition(float deltaTime); glm::vec3 getCameraEyesPosition(float deltaTime);
bool isJumping(); bool isJumping();
bool getHMDCrouchRecenterEnabled() const;
bool isAllowedToLean() const;
bool areFeetTracked() const;
bool areHipsTracked() const;
public slots: public slots:
@ -2841,23 +2862,15 @@ private:
struct FollowHelper { struct FollowHelper {
FollowHelper(); FollowHelper();
enum FollowType { CharacterController::FollowTimePerType _timeRemaining;
Rotation = 0,
Horizontal,
Vertical,
NumFollowTypes
};
float _timeRemaining[NumFollowTypes];
void deactivate(); void deactivate();
void deactivate(FollowType type); void deactivate(CharacterController::FollowType type);
void activate(); void activate(CharacterController::FollowType type, const bool snapFollow);
void activate(FollowType type);
bool isActive() const; bool isActive() const;
bool isActive(FollowType followType) const; bool isActive(CharacterController::FollowType followType) const;
float getMaxTimeRemaining() const;
void decrementTimeRemaining(float dt); 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 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 shouldActivateHorizontal(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const;
bool shouldActivateHorizontalCG(MyAvatar& myAvatar) const; bool shouldActivateHorizontalCG(MyAvatar& myAvatar) const;
@ -2880,7 +2893,7 @@ private:
FollowHelper _follow; FollowHelper _follow;
bool isFollowActive(FollowHelper::FollowType followType) const; bool isFollowActive(CharacterController::FollowType followType) const;
bool _goToPending { false }; bool _goToPending { false };
bool _physicsSafetyPending { false }; bool _physicsSafetyPending { false };
@ -2922,6 +2935,9 @@ private:
bool _centerOfGravityModelEnabled { true }; bool _centerOfGravityModelEnabled { true };
bool _hmdLeanRecenterEnabled { 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 }; bool _sprint { false };
AnimPose _prePhysicsRoomPose; AnimPose _prePhysicsRoomPose;
@ -2953,7 +2969,6 @@ private:
ThreadSafeValueCache<float> _userHeight { DEFAULT_AVATAR_HEIGHT }; ThreadSafeValueCache<float> _userHeight { DEFAULT_AVATAR_HEIGHT };
float _averageUserHeightSensorSpace { _userHeight.get() }; float _averageUserHeightSensorSpace { _userHeight.get() };
bool _sitStandStateChange { false }; bool _sitStandStateChange { false };
ThreadSafeValueCache<bool> _lockSitStandState { false };
// max unscaled forward movement speed // max unscaled forward movement speed
ThreadSafeValueCache<float> _defaultWalkSpeed { DEFAULT_AVATAR_MAX_WALKING_SPEED }; ThreadSafeValueCache<float> _defaultWalkSpeed { DEFAULT_AVATAR_MAX_WALKING_SPEED };
@ -2969,7 +2984,12 @@ private:
float _walkSpeedScalar { AVATAR_WALK_SPEED_SCALAR }; float _walkSpeedScalar { AVATAR_WALK_SPEED_SCALAR };
bool _isInWalkingState { false }; bool _isInWalkingState { false };
ThreadSafeValueCache<bool> _isInSittingState { false }; ThreadSafeValueCache<bool> _isInSittingState { false };
ThreadSafeValueCache<MyAvatar::SitStandModelType> _userRecenterModel { MyAvatar::SitStandModelType::Auto }; ThreadSafeValueCache<MyAvatar::AllowAvatarStandingPreference> _allowAvatarStandingPreference{
MyAvatar::AllowAvatarStandingPreference::Default
}; // The user preference of when MyAvatar may stand.
ThreadSafeValueCache<MyAvatar::AllowAvatarLeaningPreference> _allowAvatarLeaningPreference{
MyAvatar::AllowAvatarLeaningPreference::Default
}; // The user preference of when MyAvatar may lean.
float _sitStandStateTimer { 0.0f }; float _sitStandStateTimer { 0.0f };
float _squatTimer { 0.0f }; float _squatTimer { 0.0f };
float _tippingPoint { _userHeight.get() }; float _tippingPoint { _userHeight.get() };
@ -3012,7 +3032,8 @@ private:
Setting::Handle<int> _controlSchemeIndexSetting; Setting::Handle<int> _controlSchemeIndexSetting;
std::vector<Setting::Handle<QUuid>> _avatarEntityIDSettings; std::vector<Setting::Handle<QUuid>> _avatarEntityIDSettings;
std::vector<Setting::Handle<QByteArray>> _avatarEntityDataSettings; std::vector<Setting::Handle<QByteArray>> _avatarEntityDataSettings;
Setting::Handle<QString> _userRecenterModelSetting; Setting::Handle<QString> _allowAvatarStandingPreferenceSetting;
Setting::Handle<QString> _allowAvatarLeaningPreferenceSetting;
// AvatarEntities stuff: // AvatarEntities stuff:
// We cache the "map of unfortunately-formatted-binary-blobs" because they are expensive to compute // We cache the "map of unfortunately-formatted-binary-blobs" because they are expensive to compute

View file

@ -26,7 +26,9 @@ void MyCharacterController::RayShotgunResult::reset() {
walkable = true; walkable = true;
} }
MyCharacterController::MyCharacterController(std::shared_ptr<MyAvatar> avatar) { MyCharacterController::MyCharacterController(std::shared_ptr<MyAvatar> avatar,
const FollowTimePerType& followTimeRemainingPerType) :
CharacterController(followTimeRemainingPerType) {
assert(avatar); assert(avatar);
_avatar = avatar; _avatar = avatar;

View file

@ -23,7 +23,7 @@ class DetailedMotionState;
class MyCharacterController : public CharacterController { class MyCharacterController : public CharacterController {
public: public:
explicit MyCharacterController(std::shared_ptr<MyAvatar> avatar); explicit MyCharacterController(std::shared_ptr<MyAvatar> avatar, const FollowTimePerType& followTimeRemainingPerType);
~MyCharacterController (); ~MyCharacterController ();
void addToWorld() override; void addToWorld() override;

View file

@ -65,13 +65,20 @@ static AnimPose computeHipsInSensorFrame(MyAvatar* myAvatar, bool isFlying) {
return result; return result;
} }
const bool useCenterOfGravityModel =
myAvatar->getCenterOfGravityModelEnabled() && !isFlying && !myAvatar->getIsInWalkingState() &&
!myAvatar->getIsInSittingState() && myAvatar->getHMDLeanRecenterEnabled() &&
(myAvatar->getAllowAvatarLeaningPreference() != MyAvatar::AllowAvatarLeaningPreference::AlwaysNoRecenter) &&
myAvatar->getHMDCrouchRecenterEnabled();
glm::mat4 hipsMat; glm::mat4 hipsMat;
if (myAvatar->getCenterOfGravityModelEnabled() && !isFlying && !(myAvatar->getIsInWalkingState()) && !(myAvatar->getIsInSittingState()) && myAvatar->getHMDLeanRecenterEnabled()) { if (useCenterOfGravityModel) {
// then we use center of gravity model // then we use center of gravity model
hipsMat = myAvatar->deriveBodyUsingCgModel(); hipsMat = myAvatar->deriveBodyUsingCgModel();
} else { }
else {
// otherwise use the default of putting the hips under the head // otherwise use the default of putting the hips under the head
hipsMat = myAvatar->deriveBodyFromHMDSensor(); hipsMat = myAvatar->deriveBodyFromHMDSensor(true);
} }
glm::vec3 hipsPos = extractTranslation(hipsMat); glm::vec3 hipsPos = extractTranslation(hipsMat);
glm::quat hipsRot = glmExtractRotation(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 // 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 // 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; const float MIX_RATIO = 0.5f;
hipsRot = safeLerp(glmExtractRotation(avatarToSensorMat), hipsRot, MIX_RATIO); hipsRot = safeLerp(glmExtractRotation(avatarToSensorMat), hipsRot, MIX_RATIO);
} }

View file

@ -67,13 +67,13 @@ void AnimStats::updateStats(bool force) {
// print if we are recentering or not. // print if we are recentering or not.
_recenterText = "Recenter: "; _recenterText = "Recenter: ";
if (myAvatar->isFollowActive(MyAvatar::FollowHelper::Rotation)) { if (myAvatar->isFollowActive(CharacterController::FollowType::Rotation)) {
_recenterText += "Rotation "; _recenterText += "Rotation ";
} }
if (myAvatar->isFollowActive(MyAvatar::FollowHelper::Horizontal)) { if (myAvatar->isFollowActive(CharacterController::FollowType::Horizontal)) {
_recenterText += "Horizontal "; _recenterText += "Horizontal ";
} }
if (myAvatar->isFollowActive(MyAvatar::FollowHelper::Vertical)) { if (myAvatar->isFollowActive(CharacterController::FollowType::Vertical)) {
_recenterText += "Vertical "; _recenterText += "Vertical ";
} }
emit recenterTextChanged(); emit recenterTextChanged();

View file

@ -422,40 +422,40 @@ void setupPreferences() {
preferences->addPreference(preference); preferences->addPreference(preference);
} }
{ {
auto getter = [myAvatar]()->int { const IntPreference::Getter getter = [myAvatar]() -> int {
switch (myAvatar->getUserRecenterModel()) { return static_cast<int>(myAvatar->getAllowAvatarStandingPreference());
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;
}
}; };
auto setter = [myAvatar](int value) {
switch (value) { const IntPreference::Setter setter = [myAvatar](const int& value) {
case 0: myAvatar->setAllowAvatarStandingPreference(static_cast<MyAvatar::AllowAvatarStandingPreference>(value));
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;
}
}; };
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; 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]"; items << "When I'm standing"
preference->setHeading("Avatar leaning behavior"); << "Always"; // Must match the order in MyAvatar::AllowAvatarStandingPreference.
assert(items.size() == static_cast<uint>(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<int>(myAvatar->getAllowAvatarLeaningPreference());
};
const IntPreference::Setter setter = [myAvatar](const int& value) {
myAvatar->setAllowAvatarLeaningPreference(static_cast<MyAvatar::AllowAvatarLeaningPreference>(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<uint>(MyAvatar::AllowAvatarLeaningPreference::Count));
preference->setHeading("Allow my avatar to lean:");
preference->setItems(items); preference->setItems(items);
preferences->addPreference(preference); preferences->addPreference(preference);
} }

View file

@ -1855,6 +1855,16 @@ glm::vec3 Rig::deflectHandFromTorso(const glm::vec3& handPosition, const HFMJoin
return position; 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, void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnabled, bool hipsEstimated,
bool leftArmEnabled, bool rightArmEnabled, bool headEnabled, float dt, bool leftArmEnabled, bool rightArmEnabled, bool headEnabled, float dt,
const AnimPose& leftHandPose, const AnimPose& rightHandPose, const AnimPose& leftHandPose, const AnimPose& rightHandPose,
@ -2703,10 +2713,10 @@ void Rig::computeAvatarBoundingCapsule(
Extents totalExtents; Extents totalExtents;
totalExtents.reset(); totalExtents.reset();
// HACK by convention our Avatars are always modeled such that y=0 is the ground plane. // HACK by convention our Avatars are always modeled such that y=0 (GEOMETRY_GROUND_Y) is the ground plane.
// add the zero point so that our avatars will always have bounding volumes that are flush with the ground // 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) // 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 // 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. // 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 { float Rig::getUnscaledEyeHeight() const {
// Normally the model offset transform will contain the avatar scale factor, we explicitly remove it here. // 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 modelOffsetWithoutAvatarScale(glm::vec3(1.0f), getModelOffsetPose().rot(), getModelOffsetPose().trans());
AnimPose geomToRigWithoutAvatarScale = modelOffsetWithoutAvatarScale * getGeometryOffsetPose(); AnimPose geomToRigWithoutAvatarScale = modelOffsetWithoutAvatarScale * getGeometryOffsetPose();
// This factor can be used to scale distances in the geometry frame into the unscaled rig frame. // Factor 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 = GetScaleFactorGeometryToUnscaledRig();
float scaleFactor = geomToRigWithoutAvatarScale.scale().x; // in practice this always a uniform scale factor.
int headTopJoint = indexOfJoint("HeadTop_End"); int headTopJoint = indexOfJoint("HeadTop_End");
int headJoint = indexOfJoint("Head"); int headJoint = indexOfJoint("Head");
int eyeJoint = indexOfJoint("LeftEye") != -1 ? indexOfJoint("LeftEye") : indexOfJoint("RightEye"); int eyeJoint = indexOfJoint("LeftEye") != -1 ? indexOfJoint("LeftEye") : indexOfJoint("RightEye");
int toeJoint = indexOfJoint("LeftToeBase") != -1 ? indexOfJoint("LeftToeBase") : indexOfJoint("RightToeBase"); 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. // Values from the skeleton are in the geometry coordinate frame.
auto skeleton = getAnimSkeleton(); auto skeleton = getAnimSkeleton();
if (eyeJoint >= 0 && toeJoint >= 0) { 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; float eyeHeight = skeleton->getAbsoluteDefaultPose(eyeJoint).trans().y - skeleton->getAbsoluteDefaultPose(toeJoint).trans().y;
return scaleFactor * eyeHeight; return scaleFactor * eyeHeight;
} else if (eyeJoint >= 0) { } else if (eyeJoint >= 0) {
// Measure Eye joint to y = 0 plane. // Measure Eye joint to ground plane.
float eyeHeight = skeleton->getAbsoluteDefaultPose(eyeJoint).trans().y - GROUND_Y; float eyeHeight = skeleton->getAbsoluteDefaultPose(eyeJoint).trans().y - GEOMETRY_GROUND_Y;
return scaleFactor * eyeHeight; return scaleFactor * eyeHeight;
} else if (headTopJoint >= 0 && toeJoint >= 0) { } else if (headTopJoint >= 0 && toeJoint >= 0) {
// Measure from ToeBase joint to HeadTop_End joint, then remove forehead distance. // Measure from ToeBase joint to HeadTop_End joint, then remove forehead distance.
@ -2783,19 +2789,36 @@ float Rig::getUnscaledEyeHeight() const {
} else if (headTopJoint >= 0) { } else if (headTopJoint >= 0) {
// Measure from HeadTop_End joint to the ground, then remove forehead distance. // 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; 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); return scaleFactor * (headHeight - headHeight * ratio);
} else if (headJoint >= 0) { } else if (headJoint >= 0) {
// Measure Head joint to the ground, then add in distance from neck to eye. // 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 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; 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); return scaleFactor * (neckHeight + neckHeight * ratio);
} else { } else {
return DEFAULT_AVATAR_EYE_HEIGHT; 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) { void Rig::setDirectionalBlending(const QString& targetName, const glm::vec3& blendingTarget, const QString& alphaName, float alpha) {
_animVars.set(targetName, blendingTarget); _animVars.set(targetName, blendingTarget);
_animVars.set(alphaName, alpha); _animVars.set(alphaName, alpha);

View file

@ -251,6 +251,7 @@ public:
Flow& getFlow() { return _internalFlow; } Flow& getFlow() { return _internalFlow; }
float getUnscaledEyeHeight() const; float getUnscaledEyeHeight() const;
float getUnscaledHipsHeight() const;
void buildAbsoluteRigPoses(const AnimPoseVec& relativePoses, AnimPoseVec& absolutePosesOut) const; void buildAbsoluteRigPoses(const AnimPoseVec& relativePoses, AnimPoseVec& absolutePosesOut) const;
int getOverrideJointCount() const; int getOverrideJointCount() const;
@ -287,6 +288,11 @@ protected:
glm::vec3 deflectHandFromTorso(const glm::vec3& handPosition, const HFMJointShapeInfo& hipsShapeInfo, const HFMJointShapeInfo& spineShapeInfo, glm::vec3 deflectHandFromTorso(const glm::vec3& handPosition, const HFMJointShapeInfo& hipsShapeInfo, const HFMJointShapeInfo& spineShapeInfo,
const HFMJointShapeInfo& spine1ShapeInfo, const HFMJointShapeInfo& spine2ShapeInfo) const; 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 _modelOffset; // model to rig space
AnimPose _geometryOffset; // geometry to model space (includes unit offset & fst offsets) AnimPose _geometryOffset; // geometry to model space (includes unit offset & fst offsets)

View file

@ -107,12 +107,12 @@ CharacterController::CharacterMotor::CharacterMotor(const glm::vec3& vel, const
static uint32_t _numCharacterControllers { 0 }; static uint32_t _numCharacterControllers { 0 };
CharacterController::CharacterController() { CharacterController::CharacterController(const FollowTimePerType& followTimeRemainingPerType) :
_followTimeRemainingPerType(followTimeRemainingPerType) {
_floorDistance = _scaleFactor * DEFAULT_AVATAR_FALL_HEIGHT; _floorDistance = _scaleFactor * DEFAULT_AVATAR_FALL_HEIGHT;
_targetVelocity.setValue(0.0f, 0.0f, 0.0f); _targetVelocity.setValue(0.0f, 0.0f, 0.0f);
_followDesiredBodyTransform.setIdentity(); _followDesiredBodyTransform.setIdentity();
_followTimeRemaining = 0.0f;
_state = State::Hover; _state = State::Hover;
_isPushingUp = false; _isPushingUp = false;
_rayHitStartTime = 0; _rayHitStartTime = 0;
@ -351,27 +351,63 @@ void CharacterController::playerStep(btCollisionWorld* collisionWorld, btScalar
computeNewVelocity(dt, velocity); computeNewVelocity(dt, velocity);
const float MINIMUM_TIME_REMAINING = 0.005f; const float MINIMUM_TIME_REMAINING = 0.005f;
const float MAX_DISPLACEMENT = 0.5f * _radius; static_assert(FOLLOW_TIME_IMMEDIATE_SNAP > MINIMUM_TIME_REMAINING, "The code below assumes this condition is true.");
_followTimeRemaining -= dt;
if (_followTimeRemaining >= MINIMUM_TIME_REMAINING) {
btTransform bodyTransform = _rigidBody->getWorldTransform();
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 startPos = bodyTransform.getOrigin();
btVector3 deltaPos = _followDesiredBodyTransform.getOrigin() - startPos; 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<uint>(FollowType::Horizontal)];
const float verticalTime = _followTimeRemainingPerType[static_cast<uint>(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; btVector3 endPos = startPos + linearDisplacement;
// resolve the simple linearDisplacement // resolve the simple linearDisplacement
_followLinearDisplacement += linearDisplacement; _followLinearDisplacement += linearDisplacement;
// now for the rotational part... // now for the rotational part...
btQuaternion startRot = bodyTransform.getRotation(); btQuaternion startRot = bodyTransform.getRotation();
btQuaternion desiredRot = _followDesiredBodyTransform.getRotation();
// startRot as default rotation // startRot as default rotation
btQuaternion endRot = startRot; btQuaternion endRot = startRot;
const float rotationTime = _followTimeRemainingPerType[static_cast<uint>(FollowType::Rotation)];
if (rotationTime > MINIMUM_TIME_REMAINING) {
btQuaternion desiredRot = _followDesiredBodyTransform.getRotation();
// the dot product between two quaternions is equal to +/- cos(angle/2) // the dot product between two quaternions is equal to +/- cos(angle/2)
// where 'angle' is that of the rotation between them // where 'angle' is that of the rotation between them
float qDot = desiredRot.dot(startRot); float qDot = desiredRot.dot(startRot);
@ -394,8 +430,11 @@ void CharacterController::playerStep(btCollisionWorld* collisionWorld, btScalar
// compute the angle we will resolve for this dt, but don't overshoot // compute the angle we will resolve for this dt, but don't overshoot
float angle = 2.0f * acosf(qDot); float angle = 2.0f * acosf(qDot);
if (dt < _followTimeRemaining) {
angle *= dt / _followTimeRemaining; if (rotationTime != FOLLOW_TIME_IMMEDIATE_SNAP) {
if (dt < rotationTime) {
angle *= dt / rotationTime;
}
} }
// accumulate rotation // accumulate rotation
@ -406,11 +445,14 @@ void CharacterController::playerStep(btCollisionWorld* collisionWorld, btScalar
btVector3 shapeLocalOffset = glmToBullet(_shapeLocalOffset); btVector3 shapeLocalOffset = glmToBullet(_shapeLocalOffset);
endRot = deltaRot * startRot; endRot = deltaRot * startRot;
btVector3 swingDisplacement = rotateVector(endRot, -shapeLocalOffset) - rotateVector(startRot, -shapeLocalOffset); btVector3 swingDisplacement =
rotateVector(endRot, -shapeLocalOffset) - rotateVector(startRot, -shapeLocalOffset);
_followLinearDisplacement += swingDisplacement; _followLinearDisplacement += swingDisplacement;
} }
}
_rigidBody->setWorldTransform(btTransform(endRot, endPos)); _rigidBody->setWorldTransform(btTransform(endRot, endPos));
} }
_followTime += dt; _followTime += dt;
if (_steppingUp) { if (_steppingUp) {
@ -606,8 +648,7 @@ void CharacterController::setParentVelocity(const glm::vec3& velocity) {
_parentVelocity = glmToBullet(velocity); _parentVelocity = glmToBullet(velocity);
} }
void CharacterController::setFollowParameters(const glm::mat4& desiredWorldBodyMatrix, float timeRemaining) { void CharacterController::setFollowParameters(const glm::mat4& desiredWorldBodyMatrix) {
_followTimeRemaining = timeRemaining;
_followDesiredBodyTransform = glmToBullet(desiredWorldBodyMatrix) * btTransform(btQuaternion::getIdentity(), glmToBullet(_shapeLocalOffset)); _followDesiredBodyTransform = glmToBullet(desiredWorldBodyMatrix) * btTransform(btQuaternion::getIdentity(), glmToBullet(_shapeLocalOffset));
} }

View file

@ -53,7 +53,21 @@ const btScalar MIN_CHARACTER_MOTOR_TIMESCALE = 0.05f;
class CharacterController : public btCharacterControllerInterface { class CharacterController : public btCharacterControllerInterface {
public: public:
CharacterController(); enum class FollowType : uint8_t
{
Rotation,
Horizontal,
Vertical,
Count
};
// Remaining follow time for each FollowType
typedef std::array<float, static_cast<size_t>(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(); virtual ~CharacterController();
bool needsRemoval() const; bool needsRemoval() const;
bool needsAddition() const; bool needsAddition() const;
@ -99,7 +113,8 @@ public:
void getPositionAndOrientation(glm::vec3& position, glm::quat& rotation) const; void getPositionAndOrientation(glm::vec3& position, glm::quat& rotation) const;
void setParentVelocity(const glm::vec3& parentVelocity); 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; } float getFollowTime() const { return _followTime; }
glm::vec3 getFollowLinearDisplacement() const; glm::vec3 getFollowLinearDisplacement() const;
glm::quat getFollowAngularDisplacement() const; glm::quat getFollowAngularDisplacement() const;
@ -144,7 +159,7 @@ public:
void setPendingFlagsUpdateCollisionMask(){ _pendingFlags |= PENDING_FLAG_UPDATE_COLLISION_MASK; } void setPendingFlagsUpdateCollisionMask(){ _pendingFlags |= PENDING_FLAG_UPDATE_COLLISION_MASK; }
void setSeated(bool isSeated) { _isSeated = isSeated; } void setSeated(bool isSeated) { _isSeated = isSeated; }
bool getSeated() { return _isSeated; } bool getSeated() const { return _isSeated; }
void resetStuckCounter() { _numStuckSubsteps = 0; } void resetStuckCounter() { _numStuckSubsteps = 0; }
@ -178,7 +193,7 @@ protected:
btVector3 _preSimulationVelocity; btVector3 _preSimulationVelocity;
btVector3 _velocityChange; btVector3 _velocityChange;
btTransform _followDesiredBodyTransform; btTransform _followDesiredBodyTransform;
btScalar _followTimeRemaining; const FollowTimePerType& _followTimeRemainingPerType;
btTransform _characterBodyTransform; btTransform _characterBodyTransform;
btVector3 _position; btVector3 _position;
btQuaternion _rotation; btQuaternion _rotation;

View file

@ -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_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_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_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_SPINE2_SPLINE_PROPORTION = 0.71f;
const float DEFAULT_AVATAR_SUPPORT_BASE_LEFT = -0.25f; const float DEFAULT_AVATAR_SUPPORT_BASE_LEFT = -0.25f;
const float DEFAULT_AVATAR_SUPPORT_BASE_RIGHT = 0.25f; const float DEFAULT_AVATAR_SUPPORT_BASE_RIGHT = 0.25f;