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.
This commit is contained in:
Anthony J. Thibault 2017-11-28 17:20:26 -08:00
parent dff49cafaa
commit a4d70e89df
2 changed files with 41 additions and 35 deletions

View file

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

View file

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