diff --git a/interface/resources/avatar/avatar-animation.json b/interface/resources/avatar/avatar-animation.json index ee2b916d1e..44d294f767 100644 --- a/interface/resources/avatar/avatar-animation.json +++ b/interface/resources/avatar/avatar-animation.json @@ -566,7 +566,7 @@ }, { "id": "idleToWalkFwd", - "interpTarget": 3, + "interpTarget": 10, "interpDuration": 3, "transitions": [ { "var": "idleToWalkFwdOnDone", "state": "walkFwd" }, @@ -603,8 +603,8 @@ }, { "id": "walkBwd", - "interpTarget": 6, - "interpDuration": 6, + "interpTarget": 8, + "interpDuration": 2, "transitions": [ { "var": "isNotMoving", "state": "idle" }, { "var": "isMovingForward", "state": "walkFwd" }, @@ -621,8 +621,8 @@ }, { "id": "strafeRight", - "interpTarget": 6, - "interpDuration": 6, + "interpTarget": 20, + "interpDuration": 1, "transitions": [ { "var": "isNotMoving", "state": "idle" }, { "var": "isMovingForward", "state": "walkFwd" }, @@ -639,8 +639,8 @@ }, { "id": "strafeLeft", - "interpTarget": 6, - "interpDuration": 6, + "interpTarget": 20, + "interpDuration": 1, "transitions": [ { "var": "isNotMoving", "state": "idle" }, { "var": "isMovingForward", "state": "walkFwd" }, diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index d7b3f8db94..dbb1d8a56c 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -88,6 +88,10 @@ const float MyAvatar::ZOOM_MIN = 0.5f; const float MyAvatar::ZOOM_MAX = 25.0f; const float MyAvatar::ZOOM_DEFAULT = 1.5f; const float MIN_SCALE_CHANGED_DELTA = 0.001f; +const int MODE_READINGS_RING_BUFFER_SIZE = 500; +const float CENTIMETERS_PER_METER = 100.0f; + +//#define DEBUG_DRAW_HMD_MOVING_AVERAGE MyAvatar::MyAvatar(QThread* thread) : Avatar(thread), @@ -108,6 +112,7 @@ MyAvatar::MyAvatar(QThread* thread) : _hmdSensorMatrix(), _hmdSensorOrientation(), _hmdSensorPosition(), + _recentModeReadings(MODE_READINGS_RING_BUFFER_SIZE), _bodySensorMatrix(), _goToPending(false), _goToPosition(), @@ -414,7 +419,8 @@ void MyAvatar::reset(bool andRecenter, bool andReload, bool andHead) { void MyAvatar::update(float deltaTime) { // update moving average of HMD facing in xz plane. - const float HMD_FACING_TIMESCALE = 4.0f; // very slow average + const float HMD_FACING_TIMESCALE = getRotationRecenterFilterLength(); + float tau = deltaTime / HMD_FACING_TIMESCALE; _headControllerFacingMovingAverage = lerp(_headControllerFacingMovingAverage, _headControllerFacing, tau); @@ -423,6 +429,12 @@ void MyAvatar::update(float deltaTime) { _smoothOrientationTimer += deltaTime; } + float newHeightReading = getControllerPoseInAvatarFrame(controller::Action::HEAD).getTranslation().y; + int newHeightReadingInCentimeters = glm::floor(newHeightReading * CENTIMETERS_PER_METER); + _recentModeReadings.insert(newHeightReadingInCentimeters); + setCurrentStandingHeight(computeStandingHeightMode(getControllerPoseInAvatarFrame(controller::Action::HEAD))); + setAverageHeadRotation(computeAverageHeadRotation(getControllerPoseInAvatarFrame(controller::Action::HEAD))); + #ifdef DEBUG_DRAW_HMD_MOVING_AVERAGE auto sensorHeadPose = getControllerPoseInSensorFrame(controller::Action::HEAD); glm::vec3 worldHeadPos = transformPoint(getSensorToWorldMatrix(), sensorHeadPose.getTranslation()); @@ -2182,6 +2194,15 @@ void MyAvatar::setHasAudioEnabledFaceMovement(bool hasAudioEnabledFaceMovement) _headData->setHasAudioEnabledFaceMovement(hasAudioEnabledFaceMovement); } +void MyAvatar::setRotationRecenterFilterLength(float length) { + const float MINIMUM_ROTATION_RECENTER_FILTER_LENGTH = 0.01f; + _rotationRecenterFilterLength = std::max(MINIMUM_ROTATION_RECENTER_FILTER_LENGTH, length); +} + +void MyAvatar::setRotationThreshold(float angleRadians) { + _rotationThreshold = angleRadians; +} + void MyAvatar::updateOrientation(float deltaTime) { // Smoothly rotate body with arrow keys @@ -3146,6 +3167,148 @@ glm::mat4 MyAvatar::deriveBodyUsingCgModel() const { return worldToSensorMat * avatarToWorldMat * avatarHipsMat; } +static bool isInsideLine(const glm::vec3& a, const glm::vec3& b, const glm::vec3& c) { + return (((b.x - a.x) * (c.z - a.z) - (b.z - a.z) * (c.x - a.x)) > 0); +} + +static bool withinBaseOfSupport(const controller::Pose& head) { + float userScale = 1.0f; + + glm::vec3 frontLeft(-DEFAULT_AVATAR_LATERAL_STEPPING_THRESHOLD, 0.0f, -DEFAULT_AVATAR_ANTERIOR_STEPPING_THRESHOLD); + glm::vec3 frontRight(DEFAULT_AVATAR_LATERAL_STEPPING_THRESHOLD, 0.0f, -DEFAULT_AVATAR_ANTERIOR_STEPPING_THRESHOLD); + glm::vec3 backLeft(-DEFAULT_AVATAR_LATERAL_STEPPING_THRESHOLD, 0.0f, DEFAULT_AVATAR_POSTERIOR_STEPPING_THRESHOLD); + glm::vec3 backRight(DEFAULT_AVATAR_LATERAL_STEPPING_THRESHOLD, 0.0f, DEFAULT_AVATAR_POSTERIOR_STEPPING_THRESHOLD); + + bool isWithinSupport = false; + if (head.isValid()) { + bool withinFrontBase = isInsideLine(userScale * frontLeft, userScale * frontRight, head.getTranslation()); + bool withinBackBase = isInsideLine(userScale * backRight, userScale * backLeft, head.getTranslation()); + bool withinLateralBase = (isInsideLine(userScale * frontRight, userScale * backRight, head.getTranslation()) && + isInsideLine(userScale * backLeft, userScale * frontLeft, head.getTranslation())); + isWithinSupport = (withinFrontBase && withinBackBase && withinLateralBase); + } + return isWithinSupport; +} + +static bool headAngularVelocityBelowThreshold(const controller::Pose& head) { + glm::vec3 xzPlaneAngularVelocity(0.0f, 0.0f, 0.0f); + if (head.isValid()) { + xzPlaneAngularVelocity.x = head.getAngularVelocity().x; + xzPlaneAngularVelocity.z = head.getAngularVelocity().z; + } + float magnitudeAngularVelocity = glm::length(xzPlaneAngularVelocity); + bool isBelowThreshold = (magnitudeAngularVelocity < DEFAULT_AVATAR_HEAD_ANGULAR_VELOCITY_STEPPING_THRESHOLD); + + return isBelowThreshold; +} + +static bool isWithinThresholdHeightMode(const controller::Pose& head,const float& newMode) { + bool isWithinThreshold = true; + if (head.isValid()) { + isWithinThreshold = (head.getTranslation().y - newMode) > DEFAULT_AVATAR_MODE_HEIGHT_STEPPING_THRESHOLD; + } + return isWithinThreshold; +} + +float MyAvatar::computeStandingHeightMode(const controller::Pose& head) { + const float MODE_CORRECTION_FACTOR = 0.02f; + int greatestFrequency = 0; + int mode = 0; + // init mode in meters to the current mode + float modeInMeters = getCurrentStandingHeight(); + if (head.isValid()) { + std::map freq; + for(auto recentModeReadingsIterator = _recentModeReadings.begin(); recentModeReadingsIterator != _recentModeReadings.end(); ++recentModeReadingsIterator) { + freq[*recentModeReadingsIterator] += 1; + if (freq[*recentModeReadingsIterator] > greatestFrequency) { + greatestFrequency = freq[*recentModeReadingsIterator]; + mode = *recentModeReadingsIterator; + } + } + + modeInMeters = ((float)mode) / CENTIMETERS_PER_METER; + if (!(modeInMeters > getCurrentStandingHeight())) { + // if not greater check for a reset + if (getResetMode() && qApp->isHMDMode()) { + setResetMode(false); + float resetModeInCentimeters = glm::floor((head.getTranslation().y - MODE_CORRECTION_FACTOR)*CENTIMETERS_PER_METER); + modeInMeters = (resetModeInCentimeters / CENTIMETERS_PER_METER); + _recentModeReadings.clear(); + + } else { + // if not greater and no reset, keep the mode as it is + modeInMeters = getCurrentStandingHeight(); + + } + } + } + return modeInMeters; +} + +static bool handDirectionMatchesHeadDirection(const controller::Pose& leftHand, const controller::Pose& rightHand, const controller::Pose& head) { + const float VELOCITY_EPSILON = 0.02f; + bool leftHandDirectionMatchesHead = true; + bool rightHandDirectionMatchesHead = true; + glm::vec3 xzHeadVelocity(head.velocity.x, 0.0f, head.velocity.z); + if (leftHand.isValid() && head.isValid()) { + glm::vec3 xzLeftHandVelocity(leftHand.velocity.x, 0.0f, leftHand.velocity.z); + if ((glm::length(xzLeftHandVelocity) > VELOCITY_EPSILON) && (glm::length(xzHeadVelocity) > VELOCITY_EPSILON)) { + float handDotHeadLeft = glm::dot(glm::normalize(xzLeftHandVelocity), glm::normalize(xzHeadVelocity)); + leftHandDirectionMatchesHead = ((handDotHeadLeft > DEFAULT_HANDS_VELOCITY_DIRECTION_STEPPING_THRESHOLD)); + } else { + leftHandDirectionMatchesHead = false; + } + } + if (rightHand.isValid() && head.isValid()) { + glm::vec3 xzRightHandVelocity(rightHand.velocity.x, 0.0f, rightHand.velocity.z); + if ((glm::length(xzRightHandVelocity) > VELOCITY_EPSILON) && (glm::length(xzHeadVelocity) > VELOCITY_EPSILON)) { + float handDotHeadRight = glm::dot(glm::normalize(xzRightHandVelocity), glm::normalize(xzHeadVelocity)); + rightHandDirectionMatchesHead = (handDotHeadRight > DEFAULT_HANDS_VELOCITY_DIRECTION_STEPPING_THRESHOLD); + } else { + rightHandDirectionMatchesHead = false; + } + } + return leftHandDirectionMatchesHead && rightHandDirectionMatchesHead; +} + +static bool handAngularVelocityBelowThreshold(const controller::Pose& leftHand, const controller::Pose& rightHand) { + float leftHandXZAngularVelocity = 0.0f; + float rightHandXZAngularVelocity = 0.0f; + if (leftHand.isValid()) { + glm::vec3 xzLeftHandAngularVelocity(leftHand.angularVelocity.x, 0.0f, leftHand.angularVelocity.z); + leftHandXZAngularVelocity = glm::length(xzLeftHandAngularVelocity); + } + if (rightHand.isValid()) { + glm::vec3 xzRightHandAngularVelocity(rightHand.angularVelocity.x, 0.0f, rightHand.angularVelocity.z); + rightHandXZAngularVelocity = glm::length(xzRightHandAngularVelocity); + } + return ((leftHandXZAngularVelocity < DEFAULT_HANDS_ANGULAR_VELOCITY_STEPPING_THRESHOLD) && + (rightHandXZAngularVelocity < DEFAULT_HANDS_ANGULAR_VELOCITY_STEPPING_THRESHOLD)); +} + +static bool headVelocityGreaterThanThreshold(const controller::Pose& head) { + float headVelocityMagnitude = 0.0f; + if (head.isValid()) { + headVelocityMagnitude = glm::length(head.getVelocity()); + } + return headVelocityMagnitude > DEFAULT_HEAD_VELOCITY_STEPPING_THRESHOLD; +} + +glm::quat MyAvatar::computeAverageHeadRotation(const controller::Pose& head) { + const float AVERAGING_RATE = 0.03f; + return safeLerp(_averageHeadRotation, head.getRotation(), AVERAGING_RATE); +} + +static bool isHeadLevel(const controller::Pose& head, const glm::quat& averageHeadRotation) { + glm::vec3 diffFromAverageEulers(0.0f, 0.0f, 0.0f); + if (head.isValid()) { + glm::vec3 averageHeadEulers = glm::degrees(safeEulerAngles(averageHeadRotation)); + glm::vec3 currentHeadEulers = glm::degrees(safeEulerAngles(head.getRotation())); + diffFromAverageEulers = averageHeadEulers - currentHeadEulers; + } + return ((fabs(diffFromAverageEulers.x) < DEFAULT_HEAD_PITCH_STEPPING_TOLERANCE) && (fabs(diffFromAverageEulers.z) < DEFAULT_HEAD_ROLL_STEPPING_TOLERANCE)); +} + float MyAvatar::getUserHeight() const { return _userHeight.get(); } @@ -3312,7 +3475,7 @@ void MyAvatar::FollowHelper::decrementTimeRemaining(float dt) { } bool MyAvatar::FollowHelper::shouldActivateRotation(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const { - const float FOLLOW_ROTATION_THRESHOLD = cosf(PI / 6.0f); // 30 degrees + const float FOLLOW_ROTATION_THRESHOLD = cosf(myAvatar.getRotationThreshold()); glm::vec2 bodyFacing = getFacingDir2D(currentBodyMatrix); return glm::dot(-myAvatar.getHeadControllerFacingMovingAverage(), bodyFacing) < FOLLOW_ROTATION_THRESHOLD; } @@ -3339,7 +3502,37 @@ bool MyAvatar::FollowHelper::shouldActivateHorizontal(const MyAvatar& myAvatar, } return fabs(lateralLeanAmount) > MAX_LATERAL_LEAN; +} +bool MyAvatar::FollowHelper::shouldActivateHorizontalCG(MyAvatar& myAvatar) const { + + // get the current readings + controller::Pose currentHeadPose = myAvatar.getControllerPoseInAvatarFrame(controller::Action::HEAD); + controller::Pose currentLeftHandPose = myAvatar.getControllerPoseInAvatarFrame(controller::Action::LEFT_HAND); + controller::Pose currentRightHandPose = myAvatar.getControllerPoseInAvatarFrame(controller::Action::RIGHT_HAND); + + bool stepDetected = false; + if (!withinBaseOfSupport(currentHeadPose) && + headAngularVelocityBelowThreshold(currentHeadPose) && + isWithinThresholdHeightMode(currentHeadPose, myAvatar.getCurrentStandingHeight()) && + handDirectionMatchesHeadDirection(currentLeftHandPose, currentRightHandPose, currentHeadPose) && + handAngularVelocityBelowThreshold(currentLeftHandPose, currentRightHandPose) && + headVelocityGreaterThanThreshold(currentHeadPose) && + isHeadLevel(currentHeadPose, myAvatar.getAverageHeadRotation())) { + // a step is detected + stepDetected = true; + } else { + glm::vec3 defaultHipsPosition = myAvatar.getAbsoluteDefaultJointTranslationInObjectFrame(myAvatar.getJointIndex("Hips")); + glm::vec3 defaultHeadPosition = myAvatar.getAbsoluteDefaultJointTranslationInObjectFrame(myAvatar.getJointIndex("Head")); + glm::vec3 currentHeadPosition = currentHeadPose.getTranslation(); + float anatomicalHeadToHipsDistance = glm::length(defaultHeadPosition - defaultHipsPosition); + if (!isActive(Horizontal) && + (glm::length(currentHeadPosition - defaultHipsPosition) > (anatomicalHeadToHipsDistance + DEFAULT_AVATAR_SPINE_STRETCH_LIMIT))) { + myAvatar.setResetMode(true); + stepDetected = true; + } + } + return stepDetected; } bool MyAvatar::FollowHelper::shouldActivateVertical(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const { @@ -3359,9 +3552,16 @@ void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat if (!isActive(Rotation) && (shouldActivateRotation(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) { activate(Rotation); } - if (!isActive(Horizontal) && (shouldActivateHorizontal(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) { - activate(Horizontal); + if (myAvatar.getCenterOfGravityModelEnabled()) { + if (!isActive(Horizontal) && (shouldActivateHorizontalCG(myAvatar) || hasDriveInput)) { + activate(Horizontal); + } + } else { + if (!isActive(Horizontal) && (shouldActivateHorizontal(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) { + activate(Horizontal); + } } + if (!isActive(Vertical) && (shouldActivateVertical(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) { activate(Vertical); } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index a57de90a46..e795d9356d 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -31,6 +31,7 @@ #include "AtRestDetector.h" #include "MyCharacterController.h" +#include "RingBufferHistory.h" #include class AvatarActionHold; @@ -195,6 +196,8 @@ class MyAvatar : public Avatar { Q_PROPERTY(bool hasProceduralBlinkFaceMovement READ getHasProceduralBlinkFaceMovement WRITE setHasProceduralBlinkFaceMovement) Q_PROPERTY(bool hasProceduralEyeFaceMovement READ getHasProceduralEyeFaceMovement WRITE setHasProceduralEyeFaceMovement) Q_PROPERTY(bool hasAudioEnabledFaceMovement READ getHasAudioEnabledFaceMovement WRITE setHasAudioEnabledFaceMovement) + Q_PROPERTY(float rotationRecenterFilterLength READ getRotationRecenterFilterLength WRITE setRotationRecenterFilterLength) + Q_PROPERTY(float rotationThreshold READ getRotationThreshold WRITE setRotationThreshold) //TODO: make gravity feature work Q_PROPERTY(glm::vec3 gravity READ getGravity WRITE setGravity) Q_PROPERTY(glm::vec3 leftHandPosition READ getLeftHandPosition) @@ -880,6 +883,12 @@ public: virtual void rebuildCollisionShape() override; const glm::vec2& getHeadControllerFacingMovingAverage() const { return _headControllerFacingMovingAverage; } + float getCurrentStandingHeight() const { return _currentStandingHeight; } + void setCurrentStandingHeight(float newMode) { _currentStandingHeight = newMode; } + const glm::quat getAverageHeadRotation() const { return _averageHeadRotation; } + void setAverageHeadRotation(glm::quat rotation) { _averageHeadRotation = rotation; } + bool getResetMode() const { return _resetMode; } + void setResetMode(bool hasBeenReset) { _resetMode = hasBeenReset; } void setControllerPoseInSensorFrame(controller::Action action, const controller::Pose& pose); controller::Pose getControllerPoseInSensorFrame(controller::Action action) const; @@ -1020,6 +1029,9 @@ public: bool isReadyForPhysics() const; + float computeStandingHeightMode(const controller::Pose& head); + glm::quat computeAverageHeadRotation(const controller::Pose& head); + public slots: /**jsdoc @@ -1408,6 +1420,10 @@ private: bool getHasProceduralEyeFaceMovement() const override { return _headData->getHasProceduralEyeFaceMovement(); } void setHasAudioEnabledFaceMovement(bool hasAudioEnabledFaceMovement); bool getHasAudioEnabledFaceMovement() const override { return _headData->getHasAudioEnabledFaceMovement(); } + void setRotationRecenterFilterLength(float length); + float getRotationRecenterFilterLength() const { return _rotationRecenterFilterLength; } + void setRotationThreshold(float angleRadians); + float getRotationThreshold() const { return _rotationThreshold; } bool isMyAvatar() const override { return true; } virtual int parseDataFromBuffer(const QByteArray& buffer) override; virtual glm::vec3 getSkeletonPosition() const override; @@ -1516,6 +1532,8 @@ private: float _hmdRollControlDeadZone { ROLL_CONTROL_DEAD_ZONE_DEFAULT }; float _hmdRollControlRate { ROLL_CONTROL_RATE_DEFAULT }; std::atomic _hasScriptedBlendShapes { false }; + std::atomic _rotationRecenterFilterLength { 4.0f }; + std::atomic _rotationThreshold { 0.5235f }; // 30 degrees in radians // working copy -- see AvatarData for thread-safe _sensorToWorldMatrixCache, used for outward facing access glm::mat4 _sensorToWorldMatrix { glm::mat4() }; @@ -1527,6 +1545,11 @@ private: // cache head controller pose in sensor space glm::vec2 _headControllerFacing; // facing vector in xz plane (sensor space) glm::vec2 _headControllerFacingMovingAverage { 0.0f, 0.0f }; // facing vector in xz plane (sensor space) + glm::quat _averageHeadRotation { 0.0f, 0.0f, 0.0f, 1.0f }; + + float _currentStandingHeight { 0.0f }; + bool _resetMode { true }; + RingBufferHistory _recentModeReadings; // cache of the current body position and orientation of the avatar's body, // in sensor space. @@ -1554,6 +1577,7 @@ private: bool shouldActivateRotation(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 shouldActivateHorizontalCG(MyAvatar& myAvatar) const; void prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat4& bodySensorMatrix, const glm::mat4& currentBodyMatrix, bool hasDriveInput); glm::mat4 postPhysicsUpdate(const MyAvatar& myAvatar, const glm::mat4& currentBodyMatrix); bool getForceActivateRotation() const; @@ -1642,7 +1666,7 @@ private: // load avatar scripts once when rig is ready bool _shouldLoadScripts { false }; - bool _haveReceivedHeightLimitsFromDomain = { false }; + bool _haveReceivedHeightLimitsFromDomain { false }; }; QScriptValue audioListenModeToScriptValue(QScriptEngine* engine, const AudioListenerMode& audioListenerMode); diff --git a/libraries/shared/src/AvatarConstants.h b/libraries/shared/src/AvatarConstants.h index 58cbff6669..3dc344dc6f 100644 --- a/libraries/shared/src/AvatarConstants.h +++ b/libraries/shared/src/AvatarConstants.h @@ -24,6 +24,17 @@ const float DEFAULT_AVATAR_SUPPORT_BASE_LEFT = -0.25f; const float DEFAULT_AVATAR_SUPPORT_BASE_RIGHT = 0.25f; const float DEFAULT_AVATAR_SUPPORT_BASE_FRONT = -0.20f; const float DEFAULT_AVATAR_SUPPORT_BASE_BACK = 0.10f; +const float DEFAULT_AVATAR_LATERAL_STEPPING_THRESHOLD = 0.10f; +const float DEFAULT_AVATAR_ANTERIOR_STEPPING_THRESHOLD = 0.04f; +const float DEFAULT_AVATAR_POSTERIOR_STEPPING_THRESHOLD = 0.07f; +const float DEFAULT_AVATAR_HEAD_ANGULAR_VELOCITY_STEPPING_THRESHOLD = 0.3f; +const float DEFAULT_AVATAR_MODE_HEIGHT_STEPPING_THRESHOLD = -0.02f; +const float DEFAULT_HANDS_VELOCITY_DIRECTION_STEPPING_THRESHOLD = 0.4f; +const float DEFAULT_HANDS_ANGULAR_VELOCITY_STEPPING_THRESHOLD = 3.3f; +const float DEFAULT_HEAD_VELOCITY_STEPPING_THRESHOLD = 0.18f; +const float DEFAULT_HEAD_PITCH_STEPPING_TOLERANCE = 7.0f; +const float DEFAULT_HEAD_ROLL_STEPPING_TOLERANCE = 7.0f; +const float DEFAULT_AVATAR_SPINE_STRETCH_LIMIT = 0.07f; const float DEFAULT_AVATAR_FORWARD_DAMPENING_FACTOR = 0.5f; const float DEFAULT_AVATAR_LATERAL_DAMPENING_FACTOR = 2.0f; const float DEFAULT_AVATAR_HIPS_MASS = 40.0f;