From 7327b79ce37076acf4c69567a887dfb9c7ae1931 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 28 Nov 2017 11:37:58 -0800 Subject: [PATCH 1/4] WIP --- interface/src/avatar/MyAvatar.cpp | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index df2089223b..563bf30f76 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1956,11 +1956,14 @@ void MyAvatar::updateOrientation(float deltaTime) { // Use head/HMD roll to turn while flying, but not when standing still. if (qApp->isHMDMode() && getCharacterController()->getState() == CharacterController::State::Hover && _hmdRollControlEnabled && hasDriveInput()) { + // Turn with head roll. const float MIN_CONTROL_SPEED = 0.01f; float speed = glm::length(getWorldVelocity()); if (speed >= MIN_CONTROL_SPEED) { // Feather turn when stopping moving. + + /* AJT hack float speedFactor; if (getDriveKey(TRANSLATE_Z) != 0.0f || _lastDrivenSpeed == 0.0f) { _lastDrivenSpeed = speed; @@ -1968,15 +1971,28 @@ void MyAvatar::updateOrientation(float deltaTime) { } else { speedFactor = glm::min(speed / _lastDrivenSpeed, 1.0f); } + */ float direction = glm::dot(getWorldVelocity(), getWorldOrientation() * Vectors::UNIT_NEG_Z) > 0.0f ? 1.0f : -1.0f; - float rollAngle = glm::degrees(asinf(glm::dot(IDENTITY_UP, _hmdSensorOrientation * IDENTITY_RIGHT))); + float rollAngle = asinf(glm::dot(IDENTITY_UP, _hmdSensorOrientation * IDENTITY_RIGHT)); float rollSign = rollAngle < 0.0f ? -1.0f : 1.0f; rollAngle = fabsf(rollAngle); - rollAngle = rollAngle > _hmdRollControlDeadZone ? rollSign * (rollAngle - _hmdRollControlDeadZone) : 0.0f; + //rollAngle = rollAngle > _hmdRollControlDeadZone ? rollSign * (rollAngle - _hmdRollControlDeadZone) : 0.0f; - totalBodyYaw += speedFactor * direction * rollAngle * deltaTime * _hmdRollControlRate; + const float MAX_ROLL_ANGLE = PI / 2.0f; + const float MAX_ANGULAR_SPEED = 1.5f; // radians per sec + + // apply a quadratic ease in curve. giving less roll and shallow angles and more roll at extreme angles. + float t = glm::clamp(rollAngle, 0.0f, MAX_ROLL_ANGLE) / MAX_ROLL_ANGLE; + float newT = t;//t < 0.5f ? 2.0f * t * t : -1 + (4.0f - 2.0f * t) * t; + float angularSpeed = rollSign * newT * MAX_ANGULAR_SPEED; + + qDebug() << "AJT: rollAngle =" << rollAngle << ", t =" << t << ", newT =" << newT << ", angularSpeed =" << angularSpeed; + + // AJT: remove _hmdRollControlDeadZone, _hmdRollControlRate, _lastDrivenSpeed + + totalBodyYaw += direction * glm::degrees(angularSpeed) * deltaTime; } } From dff49cafaa547a4f864a4cad44c438b067df9b77 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 28 Nov 2017 17:11:44 -0800 Subject: [PATCH 2/4] Embiggen the stick deadspots for oculus touch controllers This effectively splits the controller into directional zones. This should make driving/flying navigation more predictable and less prone to drifting in unintentional directions, which could induce nausea. --- interface/resources/controllers/oculus_touch.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/interface/resources/controllers/oculus_touch.json b/interface/resources/controllers/oculus_touch.json index 03fc1cbefb..b818d371e3 100644 --- a/interface/resources/controllers/oculus_touch.json +++ b/interface/resources/controllers/oculus_touch.json @@ -13,11 +13,11 @@ { "from": "OculusTouch.LY", "to": "Standard.LY", "filters": [ - { "type": "deadZone", "min": 0.3 }, + { "type": "deadZone", "min": 0.7 }, "invert" ] }, - { "from": "OculusTouch.LX", "filters": { "type": "deadZone", "min": 0.3 }, "to": "Standard.LX" }, + { "from": "OculusTouch.LX", "filters": { "type": "deadZone", "min": 0.7 }, "to": "Standard.LX" }, { "from": "OculusTouch.LT", "to": "Standard.LTClick", "peek": true, "filters": [ { "type": "hysteresis", "min": 0.85, "max": 0.9 } ] @@ -29,11 +29,11 @@ { "from": "OculusTouch.RY", "to": "Standard.RY", "filters": [ - { "type": "deadZone", "min": 0.3 }, + { "type": "deadZone", "min": 0.7 }, "invert" ] }, - { "from": "OculusTouch.RX", "filters": { "type": "deadZone", "min": 0.3 }, "to": "Standard.RX" }, + { "from": "OculusTouch.RX", "filters": { "type": "deadZone", "min": 0.7 }, "to": "Standard.RX" }, { "from": "OculusTouch.RT", "to": "Standard.RTClick", "peek": true, "filters": [ { "type": "hysteresis", "min": 0.85, "max": 0.9 } ] From a4d70e89df86953545816c25440990130dba7136 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 28 Nov 2017 17:20:26 -0800 Subject: [PATCH 3/4] hmd turn roll tuning * Tighten action motor timescale while flying. This should cause less drift as the user changes direction and should lead to more comfort and control. * Hmd head roll is only enabled if you are forward or backward faster then 2 meters per second. This should prevent un-intentional turning/rolling when hovering or moving at slow speeds. * Documented hmdRoll tuning parameters accessible from JavaScript. * Removed roll feathering code, because it is non-linear and can perhaps induce nausea. * Tuned default dead spot and turning speeds to match Eagle Flight's defaults. --- interface/src/avatar/MyAvatar.cpp | 64 ++++++++++++++++--------------- interface/src/avatar/MyAvatar.h | 12 ++++-- 2 files changed, 41 insertions(+), 35 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 563bf30f76..2f608aca15 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1516,9 +1516,19 @@ void MyAvatar::updateMotors() { _characterController.clearMotors(); glm::quat motorRotation; if (_motionBehaviors & AVATAR_MOTION_ACTION_MOTOR_ENABLED) { + + const float FLYING_MOTOR_TIMESCALE = 0.05f; + const float WALKING_MOTOR_TIMESCALE = 0.2f; + const float INVALID_MOTOR_TIMESCALE = 1.0e6f; + + float horizontalMotorTimescale; + float verticalMotorTimescale; + if (_characterController.getState() == CharacterController::State::Hover || _characterController.computeCollisionGroup() == BULLET_COLLISION_GROUP_COLLISIONLESS) { motorRotation = getMyHead()->getHeadOrientation(); + horizontalMotorTimescale = FLYING_MOTOR_TIMESCALE; + verticalMotorTimescale = FLYING_MOTOR_TIMESCALE; } else { // non-hovering = walking: follow camera twist about vertical but not lift // we decompose camera's rotation and store the twist part in motorRotation @@ -1529,11 +1539,12 @@ void MyAvatar::updateMotors() { glm::quat liftRotation; swingTwistDecomposition(headOrientation, Vectors::UNIT_Y, liftRotation, motorRotation); motorRotation = orientation * motorRotation; + horizontalMotorTimescale = WALKING_MOTOR_TIMESCALE; + verticalMotorTimescale = INVALID_MOTOR_TIMESCALE; } - const float DEFAULT_MOTOR_TIMESCALE = 0.2f; - const float INVALID_MOTOR_TIMESCALE = 1.0e6f; + if (_isPushing || _isBraking || !_isBeingPushed) { - _characterController.addMotor(_actionMotorVelocity, motorRotation, DEFAULT_MOTOR_TIMESCALE, INVALID_MOTOR_TIMESCALE); + _characterController.addMotor(_actionMotorVelocity, motorRotation, horizontalMotorTimescale, verticalMotorTimescale); } else { // _isBeingPushed must be true --> disable action motor by giving it a long timescale, // otherwise it's attempt to "stand in in place" could defeat scripted motor/thrusts @@ -1958,41 +1969,31 @@ void MyAvatar::updateOrientation(float deltaTime) { if (qApp->isHMDMode() && getCharacterController()->getState() == CharacterController::State::Hover && _hmdRollControlEnabled && hasDriveInput()) { // Turn with head roll. - const float MIN_CONTROL_SPEED = 0.01f; - float speed = glm::length(getWorldVelocity()); - if (speed >= MIN_CONTROL_SPEED) { - // Feather turn when stopping moving. + const float MIN_CONTROL_SPEED = 2.0f; // meters / sec + const glm::vec3 characterForward = getWorldOrientation() * Vectors::UNIT_NEG_Z; + float forwardSpeed = glm::dot(characterForward, getWorldVelocity()); - /* AJT hack - float speedFactor; - if (getDriveKey(TRANSLATE_Z) != 0.0f || _lastDrivenSpeed == 0.0f) { - _lastDrivenSpeed = speed; - speedFactor = 1.0f; - } else { - speedFactor = glm::min(speed / _lastDrivenSpeed, 1.0f); - } - */ + // only enable roll-turns if we are moving forward or backward at greater then MIN_CONTROL_SPEED + if (fabsf(forwardSpeed) >= MIN_CONTROL_SPEED) { - float direction = glm::dot(getWorldVelocity(), getWorldOrientation() * Vectors::UNIT_NEG_Z) > 0.0f ? 1.0f : -1.0f; - - float rollAngle = asinf(glm::dot(IDENTITY_UP, _hmdSensorOrientation * IDENTITY_RIGHT)); + float direction = forwardSpeed > 0.0f ? 1.0f : -1.0f; + float rollAngle = glm::degrees(asinf(glm::dot(IDENTITY_UP, _hmdSensorOrientation * IDENTITY_RIGHT))); float rollSign = rollAngle < 0.0f ? -1.0f : 1.0f; rollAngle = fabsf(rollAngle); - //rollAngle = rollAngle > _hmdRollControlDeadZone ? rollSign * (rollAngle - _hmdRollControlDeadZone) : 0.0f; - const float MAX_ROLL_ANGLE = PI / 2.0f; - const float MAX_ANGULAR_SPEED = 1.5f; // radians per sec + const float MIN_ROLL_ANGLE = _hmdRollControlDeadZone; + const float MAX_ROLL_ANGLE = 90.0f; // degrees - // apply a quadratic ease in curve. giving less roll and shallow angles and more roll at extreme angles. - float t = glm::clamp(rollAngle, 0.0f, MAX_ROLL_ANGLE) / MAX_ROLL_ANGLE; - float newT = t;//t < 0.5f ? 2.0f * t * t : -1 + (4.0f - 2.0f * t) * t; - float angularSpeed = rollSign * newT * MAX_ANGULAR_SPEED; + if (rollAngle > MIN_ROLL_ANGLE) { + // rate of turning is linearly proportional to rolAngle + rollAngle = glm::clamp(rollAngle, MIN_ROLL_ANGLE, MAX_ROLL_ANGLE); - qDebug() << "AJT: rollAngle =" << rollAngle << ", t =" << t << ", newT =" << newT << ", angularSpeed =" << angularSpeed; + // scale rollAngle into a value from zero to one. + float t = (rollAngle - MIN_ROLL_ANGLE) / (MAX_ROLL_ANGLE - MIN_ROLL_ANGLE); - // AJT: remove _hmdRollControlDeadZone, _hmdRollControlRate, _lastDrivenSpeed - - totalBodyYaw += direction * glm::degrees(angularSpeed) * deltaTime; + float angularSpeed = rollSign * t * _hmdRollControlRate; + totalBodyYaw += direction * angularSpeed * deltaTime; + } } } @@ -2038,12 +2039,13 @@ void MyAvatar::updateActionMotor(float deltaTime) { _isBraking = _wasPushing || (_isBraking && speed > MIN_ACTION_BRAKE_SPEED); } + CharacterController::State state = _characterController.getState(); + // compute action input glm::vec3 forward = (getDriveKey(TRANSLATE_Z)) * IDENTITY_FORWARD; glm::vec3 right = (getDriveKey(TRANSLATE_X)) * IDENTITY_RIGHT; glm::vec3 direction = forward + right; - CharacterController::State state = _characterController.getState(); if (state == CharacterController::State::Hover || _characterController.computeCollisionGroup() == BULLET_COLLISION_GROUP_COLLISIONLESS) { // we can fly --> support vertical motion diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 563fb7fccd..16cb0edfad 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -110,6 +110,10 @@ class MyAvatar : public Avatar { * @property userEyeHeight {number} Estimated height of the users eyes in sensor space. (meters) * @property SELF_ID {string} READ-ONLY. UUID representing "my avatar". Only use for local-only entities and overlays in situations where MyAvatar.sessionUUID is not available (e.g., if not connected to a domain). * Note: Likely to be deprecated. + * @property hmdRollControlEnabled {bool} When enabled the roll angle of your HMD will turn your avatar while flying. + * @property hmdRollControlDeadZone {number} If hmdRollControlEnabled is true, this value can be used to tune what roll angle is required to begin turning. + * This angle is specified in degrees. + * @property hmdRollControlRate {number} If hmdRollControlEnabled is true, this value determines the maximum turn rate of your avatar when rolling your HMD in degrees per second. */ // FIXME: `glm::vec3 position` is not accessible from QML, so this exposes position in a QML-native type @@ -158,7 +162,7 @@ class MyAvatar : public Avatar { Q_PROPERTY(float userEyeHeight READ getUserEyeHeight) Q_PROPERTY(QUuid SELF_ID READ getSelfID CONSTANT) - + const QString DOMINANT_LEFT_HAND = "left"; const QString DOMINANT_RIGHT_HAND = "right"; @@ -737,12 +741,12 @@ private: bool _clearOverlayWhenMoving { true }; QString _dominantHand { DOMINANT_RIGHT_HAND }; - const float ROLL_CONTROL_DEAD_ZONE_DEFAULT = 8.0f; // deg - const float ROLL_CONTROL_RATE_DEFAULT = 2.5f; // deg/sec/deg + const float ROLL_CONTROL_DEAD_ZONE_DEFAULT = 8.0; // degrees + const float ROLL_CONTROL_RATE_DEFAULT = 114.0f; // degrees / sec + bool _hmdRollControlEnabled { true }; float _hmdRollControlDeadZone { ROLL_CONTROL_DEAD_ZONE_DEFAULT }; float _hmdRollControlRate { ROLL_CONTROL_RATE_DEFAULT }; - float _lastDrivenSpeed { 0.0f }; // working copy -- see AvatarData for thread-safe _sensorToWorldMatrixCache, used for outward facing access glm::mat4 _sensorToWorldMatrix { glm::mat4() }; From 47b52238d91bab462be296e85971bd0bb0229fea Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 7 Dec 2017 10:49:23 -0800 Subject: [PATCH 4/4] code review feedback --- interface/src/avatar/MyAvatar.cpp | 8 ++++---- interface/src/avatar/MyAvatar.h | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 2f608aca15..6d5a202128 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1969,7 +1969,7 @@ void MyAvatar::updateOrientation(float deltaTime) { if (qApp->isHMDMode() && getCharacterController()->getState() == CharacterController::State::Hover && _hmdRollControlEnabled && hasDriveInput()) { // Turn with head roll. - const float MIN_CONTROL_SPEED = 2.0f; // meters / sec + const float MIN_CONTROL_SPEED = 2.0f * getSensorToWorldScale(); // meters / sec const glm::vec3 characterForward = getWorldOrientation() * Vectors::UNIT_NEG_Z; float forwardSpeed = glm::dot(characterForward, getWorldVelocity()); @@ -1985,13 +1985,13 @@ void MyAvatar::updateOrientation(float deltaTime) { const float MAX_ROLL_ANGLE = 90.0f; // degrees if (rollAngle > MIN_ROLL_ANGLE) { - // rate of turning is linearly proportional to rolAngle + // rate of turning is linearly proportional to rollAngle rollAngle = glm::clamp(rollAngle, MIN_ROLL_ANGLE, MAX_ROLL_ANGLE); // scale rollAngle into a value from zero to one. - float t = (rollAngle - MIN_ROLL_ANGLE) / (MAX_ROLL_ANGLE - MIN_ROLL_ANGLE); + float rollFactor = (rollAngle - MIN_ROLL_ANGLE) / (MAX_ROLL_ANGLE - MIN_ROLL_ANGLE); - float angularSpeed = rollSign * t * _hmdRollControlRate; + float angularSpeed = rollSign * rollFactor * _hmdRollControlRate; totalBodyYaw += direction * angularSpeed * deltaTime; } } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 16cb0edfad..0902865f9e 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -741,7 +741,7 @@ private: bool _clearOverlayWhenMoving { true }; QString _dominantHand { DOMINANT_RIGHT_HAND }; - const float ROLL_CONTROL_DEAD_ZONE_DEFAULT = 8.0; // degrees + const float ROLL_CONTROL_DEAD_ZONE_DEFAULT = 8.0f; // degrees const float ROLL_CONTROL_RATE_DEFAULT = 114.0f; // degrees / sec bool _hmdRollControlEnabled { true };