diff --git a/interface/resources/controllers/oculus_touch.json b/interface/resources/controllers/oculus_touch.json index b818d371e3..86f559d964 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.7 }, + { "type": "deadZone", "min": 0.15 }, "invert" ] }, - { "from": "OculusTouch.LX", "filters": { "type": "deadZone", "min": 0.7 }, "to": "Standard.LX" }, + { "from": "OculusTouch.LX", "filters": { "type": "deadZone", "min": 0.15 }, "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.7 }, + { "type": "deadZone", "min": 0.15 }, "invert" ] }, - { "from": "OculusTouch.RX", "filters": { "type": "deadZone", "min": 0.7 }, "to": "Standard.RX" }, + { "from": "OculusTouch.RX", "filters": { "type": "deadZone", "min": 0.15 }, "to": "Standard.RX" }, { "from": "OculusTouch.RT", "to": "Standard.RTClick", "peek": true, "filters": [ { "type": "hysteresis", "min": 0.85, "max": 0.9 } ] diff --git a/interface/resources/controllers/standard.json b/interface/resources/controllers/standard.json index 28f15605e0..0a5bd12460 100644 --- a/interface/resources/controllers/standard.json +++ b/interface/resources/controllers/standard.json @@ -1,11 +1,14 @@ { "name": "Standard to Action", "channels": [ - { "from": "Standard.LY", "to": "Actions.TranslateZ" }, + { "from": "Standard.LY", + "when": ["Application.RightHandDominant", "!Standard.RY"], + "to": "Actions.TranslateZ" + }, { "from": "Standard.LX", "when": [ - "Application.InHMD", "!Application.AdvancedMovement", + "Application.InHMD", "!Application.AdvancedMovement", "Application.RightHandDominant", "Application.SnapTurn", "!Standard.RX" ], "to": "Actions.StepYaw", @@ -18,14 +21,14 @@ ] }, { "from": "Standard.LX", "to": "Actions.TranslateX", - "when": [ "Application.AdvancedMovement" ] + "when": [ "Application.AdvancedMovement", "Application.StrafeEnabled", "Application.RightHandDominant" ] }, { "from": "Standard.LX", "to": "Actions.Yaw", - "when": [ "!Application.AdvancedMovement", "!Application.SnapTurn" ] + "when": [ "!Application.AdvancedMovement", "!Application.SnapTurn", "Application.RightHandDominant" ] }, { "from": "Standard.RX", - "when": [ "Application.SnapTurn" ], + "when": [ "Application.SnapTurn", "Application.RightHandDominant" ], "to": "Actions.StepYaw", "filters": [ @@ -36,20 +39,69 @@ ] }, { "from": "Standard.RX", "to": "Actions.Yaw", - "when": [ "!Application.SnapTurn" ] + "when": [ "!Application.SnapTurn", "Application.RightHandDominant" ] }, + { "from": "Standard.LeftSecondaryThumb", + "when": [ "Application.Grounded", "Application.LeftHandDominant" ], + "to": "Actions.Up" + }, + + { "from": "Standard.LeftSecondaryThumb", + "when": "Application.LeftHandDominant", + "to": "Actions.Up" + }, + { "from": "Standard.RY", - "when": "Application.Grounded", - "to": "Actions.Up", + "when": ["Application.LeftHandDominant", "!Standard.LY"], + "to": "Actions.TranslateZ" + }, + + { "from": "Standard.RX", + "when": [ + "Application.InHMD", "!Application.AdvancedMovement", "Application.LeftHandDominant", + "Application.SnapTurn", "!Standard.RX" + ], + "to": "Actions.StepYaw", "filters": [ - { "type": "deadZone", "min": 0.6 }, - "invert" + { "type": "deadZone", "min": 0.15 }, + "constrainToInteger", + { "type": "pulse", "interval": 0.25 }, + { "type": "scale", "scale": 22.5 } ] }, + { "from": "Standard.RX", "to": "Actions.TranslateX", + "when": [ "Application.AdvancedMovement", "Application.StrafeEnabled", "Application.LeftHandDominant" ] + }, + { "from": "Standard.RX", "to": "Actions.Yaw", + "when": [ "!Application.AdvancedMovement", "!Application.SnapTurn", "Application.LeftHandDominant" ] + }, - { "from": "Standard.RY", "to": "Actions.Up", "filters": "invert"}, + { "from": "Standard.LX", + "when": [ "Application.SnapTurn", "Application.LeftHandDominant" ], + "to": "Actions.StepYaw", + "filters": + [ + { "type": "deadZone", "min": 0.15 }, + "constrainToInteger", + { "type": "pulse", "interval": 0.25 }, + { "type": "scale", "scale": 22.5 } + ] + }, + { "from": "Standard.LX", "to": "Actions.Yaw", + "when": [ "!Application.SnapTurn", "Application.LeftHandDominant" ] + }, + + { "from": "Standard.RightSecondaryThumb", + "when": [ "Application.Grounded", "Application.RightHandDominant" ], + "to": "Actions.Up" + }, + + { "from": "Standard.RightSecondaryThumb", + "when": "Application.RightHandDominant", + "to": "Actions.Up" + }, { "from": "Standard.Back", "to": "Actions.CycleCamera" }, { "from": "Standard.Start", "to": "Actions.ContextMenu" }, @@ -128,4 +180,4 @@ { "from": "Standard.TrackedObject14", "to" : "Actions.TrackedObject14" }, { "from": "Standard.TrackedObject15", "to" : "Actions.TrackedObject15" } ] -} \ No newline at end of file +} diff --git a/interface/resources/controllers/vive.json b/interface/resources/controllers/vive.json index 24b1587691..730e1bcb58 100644 --- a/interface/resources/controllers/vive.json +++ b/interface/resources/controllers/vive.json @@ -10,8 +10,9 @@ "filters": [ { "type": "hysteresis", "min": 0.7, "max": 0.75 } ] }, - { "from": "Vive.LY", "when": "Vive.LSY", "filters": ["invert"], "to": "Standard.LY" }, - { "from": "Vive.LX", "when": "Vive.LSX", "to": "Standard.LX" }, + { "from": "Vive.LY", "when": "Vive.LS", "filters": [ { "type": "deadZone", "min": 0.15 }, "invert" ], "to": "Standard.LY" }, + { "from": "Vive.LX", "when": ["Vive.LS", "Application.RightHandDominant"], "filters": { "type": "deadZone", "min": 0.15 }, "to": "Standard.LX" }, + { "from": "Vive.LX", "when": ["Vive.LS", "Vive.LSX", "!Vive.LSY", "Application.LeftHandDominant"], "filters": { "type": "deadZone", "min": 0.15 }, "to": "Standard.LX" }, { "from": "Vive.LT", "to": "Standard.LT", "filters": [ @@ -28,8 +29,9 @@ }, { "from": "Vive.LSTouch", "to": "Standard.LSTouch" }, - { "from": "Vive.RY", "when": "Vive.RSY", "filters": ["invert"], "to": "Standard.RY" }, - { "from": "Vive.RX", "when": "Vive.RSX", "to": "Standard.RX" }, + { "from": "Vive.RY", "when": "Vive.RS", "filters": [ { "type": "deadZone", "min": 0.15 }, "invert" ], "to": "Standard.RY" }, + { "from": "Vive.RX", "when": ["Vive.RS", "Application.LeftHandDominant"], "filters": { "type": "deadZone", "min": 0.15 }, "to": "Standard.RX" }, + { "from": "Vive.RX", "when": ["Vive.RS", "Vive.RSX", "!Vive.RSY", "Application.RightHandDominant"], "filters": { "type": "deadZone", "min": 0.15 }, "to": "Standard.RX" }, { "from": "Vive.RT", "to": "Standard.RT", "filters": [ diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index b8eb216a87..e959f87a46 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -680,6 +680,8 @@ private: * InHMDnumbernumberThe user is in HMD mode. * AdvancedMovementnumbernumberAdvanced movement controls are enabled. * + * LeftHandDominantnumbernumberDominant hand set to left. + * RightHandDominantnumbernumberDominant hand set to right. * SnapTurnnumbernumberSnap turn is enabled. * GroundednumbernumberThe user's avatar is on the ground. * NavigationFocusednumbernumberNot used. @@ -701,6 +703,9 @@ static const QString STATE_NAV_FOCUSED = "NavigationFocused"; static const QString STATE_PLATFORM_WINDOWS = "PlatformWindows"; static const QString STATE_PLATFORM_MAC = "PlatformMac"; static const QString STATE_PLATFORM_ANDROID = "PlatformAndroid"; +static const QString STATE_LEFT_HAND_DOMINANT = "LeftHandDominant"; +static const QString STATE_RIGHT_HAND_DOMINANT = "RightHandDominant"; +static const QString STATE_STRAFE_ENABLED = "StrafeEnabled"; // Statically provided display and input plugins extern DisplayPluginList getDisplayPlugins(); @@ -902,7 +907,7 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { controller::StateController::setStateVariables({ { STATE_IN_HMD, STATE_CAMERA_FULL_SCREEN_MIRROR, STATE_CAMERA_FIRST_PERSON, STATE_CAMERA_THIRD_PERSON, STATE_CAMERA_ENTITY, STATE_CAMERA_INDEPENDENT, STATE_SNAP_TURN, STATE_ADVANCED_MOVEMENT_CONTROLS, STATE_GROUNDED, STATE_NAV_FOCUSED, - STATE_PLATFORM_WINDOWS, STATE_PLATFORM_MAC, STATE_PLATFORM_ANDROID } }); + STATE_PLATFORM_WINDOWS, STATE_PLATFORM_MAC, STATE_PLATFORM_ANDROID, STATE_LEFT_HAND_DOMINANT, STATE_RIGHT_HAND_DOMINANT, STATE_STRAFE_ENABLED } }); DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); @@ -1740,6 +1745,15 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo _applicationStateDevice->setInputVariant(STATE_ADVANCED_MOVEMENT_CONTROLS, []() -> float { return qApp->getMyAvatar()->useAdvancedMovementControls() ? 1 : 0; }); + _applicationStateDevice->setInputVariant(STATE_LEFT_HAND_DOMINANT, []() -> float { + return qApp->getMyAvatar()->getDominantHand() == "left" ? 1 : 0; + }); + _applicationStateDevice->setInputVariant(STATE_RIGHT_HAND_DOMINANT, []() -> float { + return qApp->getMyAvatar()->getDominantHand() == "right" ? 1 : 0; + }); + _applicationStateDevice->setInputVariant(STATE_STRAFE_ENABLED, []() -> float { + return qApp->getMyAvatar()->getStrafeEnabled() ? 1 : 0; + }); _applicationStateDevice->setInputVariant(STATE_GROUNDED, []() -> float { return qApp->getMyAvatar()->getCharacterController()->onGround() ? 1 : 0; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 9ffcd0184e..568b492b46 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -155,6 +155,7 @@ MyAvatar::MyAvatar(QThread* thread) : _prevShouldDrawHead(true), _audioListenerMode(FROM_HEAD), _dominantHandSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "dominantHand", DOMINANT_RIGHT_HAND), + _strafeEnabledSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "strafeEnabled", DEFAULT_STRAFE_ENABLED), _hmdAvatarAlignmentTypeSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "hmdAvatarAlignmentType", DEFAULT_HMD_AVATAR_ALIGNMENT_TYPE), _headPitchSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "", 0.0f), _scaleSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "scale", _targetScale), @@ -169,7 +170,16 @@ MyAvatar::MyAvatar(QThread* thread) : _useSnapTurnSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "useSnapTurn", _useSnapTurn), _userHeightSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "userHeight", DEFAULT_AVATAR_HEIGHT), _flyingHMDSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "flyingHMD", _flyingPrefHMD), + _movementReferenceSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "movementReference", _movementReference), _avatarEntityCountSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "avatarEntityData" << "size", 0), + _driveGear1Setting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "driveGear1", _driveGear1), + _driveGear2Setting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "driveGear2", _driveGear2), + _driveGear3Setting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "driveGear3", _driveGear3), + _driveGear4Setting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "driveGear4", _driveGear4), + _driveGear5Setting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "driveGear5", _driveGear5), + _analogWalkSpeedSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "analogWalkSpeed", _analogWalkSpeed.get()), + _analogPlusWalkSpeedSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "analogPlusWalkSpeed", _analogPlusWalkSpeed.get()), + _controlSchemeIndexSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "controlSchemeIndex", _controlSchemeIndex), _userRecenterModelSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "userRecenterModel", USER_RECENTER_MODEL_AUTO) { _clientTraitsHandler.reset(new ClientTraitsHandler(this)); @@ -322,6 +332,14 @@ QString MyAvatar::getDominantHand() const { return _dominantHand.get(); } +void MyAvatar::setStrafeEnabled(bool enabled) { + _strafeEnabled.set(enabled); +} + +bool MyAvatar::getStrafeEnabled() const { + return _strafeEnabled.get(); +} + void MyAvatar::setDominantHand(const QString& hand) { if (hand == DOMINANT_LEFT_HAND || hand == DOMINANT_RIGHT_HAND) { bool changed = (hand != _dominantHand.get()); @@ -1256,6 +1274,7 @@ void MyAvatar::resizeAvatarEntitySettingHandles(uint32_t maxIndex) { void MyAvatar::saveData() { _dominantHandSetting.set(getDominantHand()); + _strafeEnabledSetting.set(getStrafeEnabled()); _hmdAvatarAlignmentTypeSetting.set(getHmdAvatarAlignmentType()); _headPitchSetting.set(getHead()->getBasePitch()); _scaleSetting.set(_targetScale); @@ -1279,6 +1298,15 @@ void MyAvatar::saveData() { _useSnapTurnSetting.set(_useSnapTurn); _userHeightSetting.set(getUserHeight()); _flyingHMDSetting.set(getFlyingHMDPref()); + _movementReferenceSetting.set(getMovementReference()); + _driveGear1Setting.set(getDriveGear1()); + _driveGear2Setting.set(getDriveGear2()); + _driveGear3Setting.set(getDriveGear3()); + _driveGear4Setting.set(getDriveGear4()); + _driveGear5Setting.set(getDriveGear5()); + _analogWalkSpeedSetting.set(getAnalogWalkSpeed()); + _analogPlusWalkSpeedSetting.set(getAnalogPlusWalkSpeed()); + _controlSchemeIndexSetting.set(getControlSchemeIndex()); _userRecenterModelSetting.set(userRecenterModelToString(getUserRecenterModel())); auto hmdInterface = DependencyManager::get(); @@ -1856,12 +1884,22 @@ void MyAvatar::loadData() { // Flying preferences must be loaded before calling setFlyingEnabled() Setting::Handle firstRunVal { Settings::firstRun, true }; setFlyingHMDPref(firstRunVal.get() ? false : _flyingHMDSetting.get()); + setMovementReference(firstRunVal.get() ? false : _movementReferenceSetting.get()); + setDriveGear1(firstRunVal.get() ? DEFAULT_GEAR_1 : _driveGear1Setting.get()); + setDriveGear2(firstRunVal.get() ? DEFAULT_GEAR_2 : _driveGear2Setting.get()); + setDriveGear3(firstRunVal.get() ? DEFAULT_GEAR_3 : _driveGear3Setting.get()); + setDriveGear4(firstRunVal.get() ? DEFAULT_GEAR_4 : _driveGear4Setting.get()); + setDriveGear5(firstRunVal.get() ? DEFAULT_GEAR_5 : _driveGear5Setting.get()); + setControlSchemeIndex(firstRunVal.get() ? LocomotionControlsMode::CONTROLS_DEFAULT : _controlSchemeIndexSetting.get()); + setAnalogWalkSpeed(firstRunVal.get() ? ANALOG_AVATAR_MAX_WALKING_SPEED : _analogWalkSpeedSetting.get()); + setAnalogPlusWalkSpeed(firstRunVal.get() ? ANALOG_PLUS_AVATAR_MAX_WALKING_SPEED : _analogPlusWalkSpeedSetting.get()); setFlyingEnabled(getFlyingEnabled()); setDisplayName(_displayNameSetting.get()); setCollisionSoundURL(_collisionSoundURLSetting.get(QUrl(DEFAULT_AVATAR_COLLISION_SOUND_URL)).toString()); setSnapTurn(_useSnapTurnSetting.get()); setDominantHand(_dominantHandSetting.get(DOMINANT_RIGHT_HAND).toLower()); + setStrafeEnabled(_strafeEnabledSetting.get(DEFAULT_STRAFE_ENABLED)); setHmdAvatarAlignmentType(_hmdAvatarAlignmentTypeSetting.get(DEFAULT_HMD_AVATAR_ALIGNMENT_TYPE).toLower()); setUserHeight(_userHeightSetting.get(DEFAULT_AVATAR_HEIGHT)); setTargetScale(_scaleSetting.get()); @@ -2519,6 +2557,12 @@ controller::Pose MyAvatar::getControllerPoseInAvatarFrame(controller::Action act } } +glm::quat MyAvatar::getOffHandRotation() const { + auto hand = (getDominantHand() == DOMINANT_RIGHT_HAND) ? controller::Action::LEFT_HAND : controller::Action::RIGHT_HAND; + auto pose = getControllerPoseInAvatarFrame(hand); + return pose.rotation; +} + void MyAvatar::updateMotors() { _characterController.clearMotors(); glm::quat motorRotation; @@ -3285,21 +3329,131 @@ void MyAvatar::updateOrientation(float deltaTime) { } } -static float scaleSpeedByDirection(const glm::vec2 velocityDirection, const float forwardSpeed, const float backwardSpeed) { - // for the elipse function --> (x^2)/(backwardSpeed*backwardSpeed) + y^2/(forwardSpeed*forwardSpeed) = 1, scale == y^2 when x is 0 - float fwdScale = forwardSpeed * forwardSpeed; - float backScale = backwardSpeed * backwardSpeed; - float scaledX = velocityDirection.x * backwardSpeed; - float scaledSpeed = forwardSpeed; - if (velocityDirection.y < 0.0f) { - if (backScale > 0.0f) { - float yValue = sqrtf(fwdScale * (1.0f - ((scaledX * scaledX) / backScale))); - scaledSpeed = sqrtf((scaledX * scaledX) + (yValue * yValue)); +float MyAvatar::calculateGearedSpeed(const float driveKey) { + float absDriveKey = abs(driveKey); + float sign = (driveKey < 0.0f) ? -1.0f : 1.0f; + if (absDriveKey > getDriveGear5()) { + return sign * 1.0f; + } + else if (absDriveKey > getDriveGear4()) { + return sign * 0.8f; + } + else if (absDriveKey > getDriveGear3()) { + return sign * 0.6f; + } + else if (absDriveKey > getDriveGear2()) { + return sign * 0.4f; + } + else if (absDriveKey > getDriveGear1()) { + return sign * 0.2f; + } + else { + return sign * 0.0f; + } +} + +glm::vec3 MyAvatar::scaleMotorSpeed(const glm::vec3 forward, const glm::vec3 right) { + float stickFullOn = 0.85f; + auto zSpeed = getDriveKey(TRANSLATE_Z); + auto xSpeed = getDriveKey(TRANSLATE_X); + glm::vec3 direction; + if (!useAdvancedMovementControls() && qApp->isHMDMode()) { + // Walking disabled in settings. + return Vectors::ZERO; + } else if (qApp->isHMDMode()) { + // HMD advanced movement controls. + switch (_controlSchemeIndex) { + case LocomotionControlsMode::CONTROLS_DEFAULT: + // No acceleration curve for this one, constant speed. + if (zSpeed || xSpeed) { + direction = (zSpeed * forward) + (xSpeed * right); + // Normalize direction. + auto length = glm::length(direction); + if (length > EPSILON) { + direction /= length; + } + return getSensorToWorldScale() * direction * getSprintSpeed() * _walkSpeedScalar; + } else { + return Vectors::ZERO; + } + case LocomotionControlsMode::CONTROLS_ANALOG: + case LocomotionControlsMode::CONTROLS_ANALOG_PLUS: + if (zSpeed || xSpeed) { + glm::vec3 scaledForward = getSensorToWorldScale() * calculateGearedSpeed(zSpeed) * _walkSpeedScalar * ((zSpeed >= stickFullOn) ? getSprintSpeed() : getWalkSpeed()) * forward; + glm::vec3 scaledRight = getSensorToWorldScale() * calculateGearedSpeed(xSpeed) * _walkSpeedScalar * ((xSpeed > stickFullOn) ? getSprintSpeed() : getWalkSpeed()) * right; + direction = scaledForward + scaledRight; + return direction; + } else { + return Vectors::ZERO; + } + default: + qDebug() << "Invalid control scheme index."; + return Vectors::ZERO; } } else { - scaledSpeed = backwardSpeed; + // Desktop mode. + direction = (zSpeed * forward) + (xSpeed * right); + auto length = glm::length(direction); + if (length > EPSILON) { + direction /= length; + } + direction *= getWalkSpeed() * _walkSpeedScalar; + return direction; } - return scaledSpeed; +} + +glm::vec3 MyAvatar::calculateScaledDirection(){ + CharacterController::State state = _characterController.getState(); + + // compute action input + // Determine if we're head or controller relative... + glm::vec3 forward, right; + + if (qApp->isHMDMode()) { + auto handRotation = getOffHandRotation(); + glm::vec3 controllerForward(0.0f, 1.0f, 0.0f); + glm::vec3 controllerRight(0.0f, 0.0f, (getDominantHand() == DOMINANT_RIGHT_HAND ? 1.0f : -1.0f)); + glm::vec3 transform; + switch (getMovementReference()) { + case LocomotionRelativeMovementMode::MOVEMENT_HAND_RELATIVE: + forward = (handRotation * controllerForward); + right = (handRotation * controllerRight); + break; + case LocomotionRelativeMovementMode::MOVEMENT_HAND_RELATIVE_LEVELED: + forward = (handRotation * controllerForward); + transform = forward - (glm::dot(forward, Vectors::UNIT_Y) * Vectors::UNIT_Y); + if (glm::length(transform) > EPSILON) { + forward = glm::normalize(transform); + } else { + forward = Vectors::ZERO; + } + right = (handRotation * controllerRight); + transform = right - (glm::dot(right, Vectors::UNIT_Y) * Vectors::UNIT_Y); + if (glm::length(transform) > EPSILON) { + right = glm::normalize(transform); + } else { + right = Vectors::ZERO; + } + break; + case LocomotionRelativeMovementMode::MOVEMENT_HMD_RELATIVE: + default: + forward = IDENTITY_FORWARD; + right = IDENTITY_RIGHT; + } + } else { + forward = IDENTITY_FORWARD; + right = IDENTITY_RIGHT; + } + + glm::vec3 direction = scaleMotorSpeed(forward, right); + + if (state == CharacterController::State::Hover || + _characterController.computeCollisionMask() == BULLET_COLLISION_MASK_COLLISIONLESS) { + glm::vec3 up = (getDriveKey(TRANSLATE_Y)) * IDENTITY_UP; + direction += up; + } + + return direction; } void MyAvatar::updateActionMotor(float deltaTime) { @@ -3319,25 +3473,13 @@ void MyAvatar::updateActionMotor(float deltaTime) { 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; - if (state == CharacterController::State::Hover || - _characterController.computeCollisionMask() == BULLET_COLLISION_MASK_COLLISIONLESS) { - glm::vec3 up = (getDriveKey(TRANSLATE_Y)) * IDENTITY_UP; - direction += up; - } + glm::vec3 direction = calculateScaledDirection(); _wasPushing = _isPushing; float directionLength = glm::length(direction); _isPushing = directionLength > EPSILON; - // normalize direction - if (_isPushing) { - direction /= directionLength; - } else { + if (!_isPushing) { direction = Vectors::ZERO; } @@ -3353,6 +3495,7 @@ void MyAvatar::updateActionMotor(float deltaTime) { const float maxBoostSpeed = sensorToWorldScale * MAX_BOOST_SPEED; if (_isPushing) { + direction /= directionLength; if (motorSpeed < maxBoostSpeed) { // an active action motor should never be slower than this float boostCoefficient = (maxBoostSpeed - motorSpeed) / maxBoostSpeed; @@ -3363,11 +3506,17 @@ void MyAvatar::updateActionMotor(float deltaTime) { } _actionMotorVelocity = motorSpeed * direction; } else { - // we're interacting with a floor --> simple horizontal speed and exponential decay - const glm::vec2 currentVel = { direction.x, direction.z }; - float scaledSpeed = scaleSpeedByDirection(currentVel, _walkSpeed.get(), _walkBackwardSpeed.get()); - // _walkSpeedScalar is a multiplier if we are in sprint mode, otherwise 1.0 - _actionMotorVelocity = sensorToWorldScale * (scaledSpeed * _walkSpeedScalar) * direction; + _actionMotorVelocity = direction; + } + + float previousBoomLength = _boomLength; + float boomChange = getDriveKey(ZOOM); + _boomLength += 2.0f * _boomLength * boomChange + boomChange * boomChange; + _boomLength = glm::clamp(_boomLength, ZOOM_MIN, ZOOM_MAX); + + // May need to change view if boom length has changed + if (previousBoomLength != _boomLength) { + qApp->changeViewAsNeeded(_boomLength); } } @@ -3880,6 +4029,136 @@ void MyAvatar::setFlyingHMDPref(bool enabled) { _flyingPrefHMD = enabled; } +void MyAvatar::setMovementReference(int enabled) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "setMovementReference", Q_ARG(bool, enabled)); + return; + } + _movementReference = enabled; +} + +int MyAvatar::getMovementReference() { + return _movementReference; +} + +void MyAvatar::setControlSchemeIndex(int index){ + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "setControlSchemeIndex", Q_ARG(int, index)); + return; + } + // Need to add checks for valid indices. + _controlSchemeIndex = index; +} + +int MyAvatar::getControlSchemeIndex() { + return _controlSchemeIndex; +} + +void MyAvatar::setDriveGear1(float shiftPoint) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "setDriveGear1", Q_ARG(float, shiftPoint)); + return; + } + if (shiftPoint > 1.0f || shiftPoint < 0.0f) return; + _driveGear1 = (shiftPoint < _driveGear2) ? shiftPoint : _driveGear1; +} + +float MyAvatar::getDriveGear1() { + switch (_controlSchemeIndex) { + case LocomotionControlsMode::CONTROLS_ANALOG: + return ANALOG_AVATAR_GEAR_1; + case LocomotionControlsMode::CONTROLS_ANALOG_PLUS: + return _driveGear1; + case LocomotionControlsMode::CONTROLS_DEFAULT: + default: + return 1.0f; + } +} + +void MyAvatar::setDriveGear2(float shiftPoint) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "setDriveGear2", Q_ARG(float, shiftPoint)); + return; + } + if (shiftPoint > 1.0f || shiftPoint < 0.0f) return; + _driveGear2 = (shiftPoint < _driveGear3 && shiftPoint >= _driveGear1) ? shiftPoint : _driveGear2; +} + +float MyAvatar::getDriveGear2() { + switch (_controlSchemeIndex) { + case LocomotionControlsMode::CONTROLS_ANALOG: + return ANALOG_AVATAR_GEAR_2; + case LocomotionControlsMode::CONTROLS_ANALOG_PLUS: + return _driveGear2; + case LocomotionControlsMode::CONTROLS_DEFAULT: + default: + return 1.0f; + } +} + +void MyAvatar::setDriveGear3(float shiftPoint) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "setDriveGear3", Q_ARG(float, shiftPoint)); + return; + } + if (shiftPoint > 1.0f || shiftPoint < 0.0f) return; + _driveGear3 = (shiftPoint < _driveGear4 && shiftPoint >= _driveGear2) ? shiftPoint : _driveGear3; +} + +float MyAvatar::getDriveGear3() { + switch (_controlSchemeIndex) { + case LocomotionControlsMode::CONTROLS_ANALOG: + return ANALOG_AVATAR_GEAR_3; + case LocomotionControlsMode::CONTROLS_ANALOG_PLUS: + return _driveGear3; + case LocomotionControlsMode::CONTROLS_DEFAULT: + default: + return 1.0f; + } +} + +void MyAvatar::setDriveGear4(float shiftPoint) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "setDriveGear4", Q_ARG(float, shiftPoint)); + return; + } + if (shiftPoint > 1.0f || shiftPoint < 0.0f) return; + _driveGear4 = (shiftPoint < _driveGear5 && shiftPoint >= _driveGear3) ? shiftPoint : _driveGear4; +} + +float MyAvatar::getDriveGear4() { + switch (_controlSchemeIndex) { + case LocomotionControlsMode::CONTROLS_ANALOG: + return ANALOG_AVATAR_GEAR_4; + case LocomotionControlsMode::CONTROLS_ANALOG_PLUS: + return _driveGear4; + case LocomotionControlsMode::CONTROLS_DEFAULT: + default: + return 1.0f; + } +} + +void MyAvatar::setDriveGear5(float shiftPoint) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "setDriveGear5", Q_ARG(float, shiftPoint)); + return; + } + if (shiftPoint > 1.0f || shiftPoint < 0.0f) return; + _driveGear5 = (shiftPoint > _driveGear4) ? shiftPoint : _driveGear5; +} + +float MyAvatar::getDriveGear5() { + switch (_controlSchemeIndex) { + case LocomotionControlsMode::CONTROLS_ANALOG: + return ANALOG_AVATAR_GEAR_5; + case LocomotionControlsMode::CONTROLS_ANALOG_PLUS: + return _driveGear5; + case LocomotionControlsMode::CONTROLS_DEFAULT: + default: + return 1.0f; + } +} + bool MyAvatar::getFlyingHMDPref() { return _flyingPrefHMD; } @@ -4488,11 +4767,37 @@ bool MyAvatar::getIsSitStandStateLocked() const { } float MyAvatar::getWalkSpeed() const { - return _walkSpeed.get() * _walkSpeedScalar; + if (qApp->isHMDMode()) { + switch (_controlSchemeIndex) { + case LocomotionControlsMode::CONTROLS_ANALOG: + return _analogWalkSpeed.get(); + case LocomotionControlsMode::CONTROLS_ANALOG_PLUS: + return _analogPlusWalkSpeed.get(); + case LocomotionControlsMode::CONTROLS_DEFAULT: + default: + return _defaultWalkSpeed.get(); + } + } else { + return _defaultWalkSpeed.get(); + } + } float MyAvatar::getWalkBackwardSpeed() const { - return _walkSpeed.get() * _walkSpeedScalar; + if (qApp->isHMDMode()) { + switch (_controlSchemeIndex) { + case LocomotionControlsMode::CONTROLS_ANALOG: + return _analogWalkBackwardSpeed.get(); + case LocomotionControlsMode::CONTROLS_ANALOG_PLUS: + return _analogPlusWalkBackwardSpeed.get(); + case LocomotionControlsMode::CONTROLS_DEFAULT: + default: + return _defaultWalkBackwardSpeed.get(); + } + } else { + return _defaultWalkBackwardSpeed.get(); + } + } bool MyAvatar::isReadyForPhysics() const { @@ -4500,7 +4805,7 @@ bool MyAvatar::isReadyForPhysics() const { } void MyAvatar::setSprintMode(bool sprint) { - _walkSpeedScalar = sprint ? _sprintSpeed.get() : AVATAR_WALK_SPEED_SCALAR; + _walkSpeedScalar = sprint ? AVATAR_SPRINT_SPEED_SCALAR : AVATAR_WALK_SPEED_SCALAR; } void MyAvatar::setIsInWalkingState(bool isWalking) { @@ -4563,19 +4868,103 @@ void MyAvatar::setIsSitStandStateLocked(bool isLocked) { } void MyAvatar::setWalkSpeed(float value) { - _walkSpeed.set(value); + switch (_controlSchemeIndex) { + case LocomotionControlsMode::CONTROLS_DEFAULT: + _defaultWalkSpeed.set(value); + break; + case LocomotionControlsMode::CONTROLS_ANALOG: + _analogWalkSpeed.set(value); + break; + case LocomotionControlsMode::CONTROLS_ANALOG_PLUS: + _analogPlusWalkSpeed.set(value); + break; + default: + break; + } } void MyAvatar::setWalkBackwardSpeed(float value) { - _walkBackwardSpeed.set(value); + switch (_controlSchemeIndex) { + case LocomotionControlsMode::CONTROLS_DEFAULT: + _defaultWalkBackwardSpeed.set(value); + break; + case LocomotionControlsMode::CONTROLS_ANALOG: + _analogWalkBackwardSpeed.set(value); + break; + case LocomotionControlsMode::CONTROLS_ANALOG_PLUS: + _analogPlusWalkBackwardSpeed.set(value); + break; + default: + break; + } } void MyAvatar::setSprintSpeed(float value) { - _sprintSpeed.set(value); + switch (_controlSchemeIndex) { + case LocomotionControlsMode::CONTROLS_DEFAULT: + _defaultSprintSpeed.set(value); + break; + case LocomotionControlsMode::CONTROLS_ANALOG: + _analogSprintSpeed.set(value); + break; + case LocomotionControlsMode::CONTROLS_ANALOG_PLUS: + _analogPlusSprintSpeed.set(value); + break; + default: + break; + } } float MyAvatar::getSprintSpeed() const { - return _sprintSpeed.get(); + if (qApp->isHMDMode()) { + switch (_controlSchemeIndex) { + case LocomotionControlsMode::CONTROLS_ANALOG: + return _analogSprintSpeed.get(); + case LocomotionControlsMode::CONTROLS_ANALOG_PLUS: + return _analogPlusSprintSpeed.get(); + case LocomotionControlsMode::CONTROLS_DEFAULT: + default: + return _defaultSprintSpeed.get(); + } + } else { + return _defaultSprintSpeed.get(); + } +} + +void MyAvatar::setAnalogWalkSpeed(float value) { + _analogWalkSpeed.set(value); + // Sprint speed for Analog should be double walk speed. + _analogSprintSpeed.set(value * 2.0f); +} + +float MyAvatar::getAnalogWalkSpeed() const { + return _analogWalkSpeed.get(); +} + +void MyAvatar::setAnalogSprintSpeed(float value) { + _analogSprintSpeed.set(value); +} + +float MyAvatar::getAnalogSprintSpeed() const { + return _analogSprintSpeed.get(); +} + +void MyAvatar::setAnalogPlusWalkSpeed(float value) { + _analogPlusWalkSpeed.set(value); + // Sprint speed for Analog Plus should be double walk speed. + _analogPlusSprintSpeed.set(value * 2.0f); +} + +float MyAvatar::getAnalogPlusWalkSpeed() const { + return _analogPlusWalkSpeed.get(); +} + +void MyAvatar::setAnalogPlusSprintSpeed(float value) { + _analogPlusSprintSpeed.set(value); +} + +float MyAvatar::getAnalogPlusSprintSpeed() const { + return _analogPlusSprintSpeed.get(); } void MyAvatar::setSitStandStateChange(bool stateChanged) { diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 0859c20153..804e2687e7 100755 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -39,6 +39,18 @@ class ModelItemID; class MyHead; class DetailedMotionState; +enum LocomotionControlsMode { + CONTROLS_DEFAULT = 0, + CONTROLS_ANALOG, + CONTROLS_ANALOG_PLUS +}; + +enum LocomotionRelativeMovementMode { + MOVEMENT_HMD_RELATIVE = 0, + MOVEMENT_HAND_RELATIVE, + MOVEMENT_HAND_RELATIVE_LEVELED +}; + enum eyeContactTarget { LEFT_EYE, RIGHT_EYE, @@ -371,6 +383,13 @@ class MyAvatar : public Avatar { using Clock = std::chrono::system_clock; using TimePoint = Clock::time_point; + const float DEFAULT_GEAR_1 = 0.2f; + const float DEFAULT_GEAR_2 = 0.4f; + const float DEFAULT_GEAR_3 = 0.8f; + const float DEFAULT_GEAR_4 = 0.9f; + const float DEFAULT_GEAR_5 = 1.0f; + + const bool DEFAULT_STRAFE_ENABLED = true; public: /**jsdoc @@ -729,7 +748,17 @@ public: */ Q_INVOKABLE void setSnapTurn(bool on) { _useSnapTurn = on; } + /** + * @function MyAvatar.getControlScheme + * @returns {number} + */ + Q_INVOKABLE int getControlScheme() const { return _controlSchemeIndex; } + /** + * @function MyAvatar.setControlScheme + * @param {number} index + */ + Q_INVOKABLE void setControlScheme(int index) { _controlSchemeIndex = (index >= 0 && index <= 2) ? index : 0; } /**jsdoc * Sets the avatar's dominant hand. * @function MyAvatar.setDominantHand @@ -744,7 +773,16 @@ public: * @returns {string} "left" for the left hand, "right" for the right hand. */ Q_INVOKABLE QString getDominantHand() const; - + /**jsdoc + * @function MyAVatar.setStrafeEnabled + * @param {bool} enabled + */ + Q_INVOKABLE void setStrafeEnabled(bool enabled); + /**jsdoc + * @function MyAvatar.getStrafeEnabled + * @returns {bool} + */ + Q_INVOKABLE bool getStrafeEnabled() const; /**jsdoc * @function MyAvatar.setHmdAvatarAlignmentType * @param {string} type - "head" to align your head and your avatar's head, "eyes" to align your @@ -1235,6 +1273,7 @@ public: controller::Pose getControllerPoseInSensorFrame(controller::Action action) const; controller::Pose getControllerPoseInWorldFrame(controller::Action action) const; controller::Pose getControllerPoseInAvatarFrame(controller::Action action) const; + glm::quat getOffHandRotation() const; bool hasDriveInput() const; @@ -1317,6 +1356,106 @@ public: */ Q_INVOKABLE bool getFlyingHMDPref(); + /**jsdoc + * Set your preference for hand-relative movement. + * @function MyAvatar.setHandRelativeMovement + * @param {number} enabled - Set true if you want to enable hand-relative movement, otherwise set to false. + * + */ + Q_INVOKABLE void setMovementReference(int enabled); + + /**jsdoc + * Get your preference for hand-relative movement. + * @function MyAvatar.getHandRelativeMovement + * @returns {number} true if your preference is for user locomotion to be relative to the direction your + * controller is pointing, otherwise false. + */ + Q_INVOKABLE int getMovementReference(); + + /**jsdoc + * Set the first 'shifting point' for acceleration step function. + * @function MyAvatar.setDriveGear1 + * @param {number} shiftPoint - Set the first shift point for analog movement acceleration step function, between [0.0, 1.0]. Must be less than or equal to Gear 2. + */ + Q_INVOKABLE void setDriveGear1(float shiftPoint); + + /**jsdoc + * Get the first 'shifting point' for acceleration step function. + * @function MyAvatar.getDriveGear1 + * @returns {number} Value between [0.0, 1.0]. + */ + Q_INVOKABLE float getDriveGear1(); + + /**jsdoc + * Set the second 'shifting point' for acceleration step function. + * @function MyAvatar.setDriveGear2 + * @param {number} shiftPoint - Defines the second shift point for analog movement acceleration step function, between [0, 1]. Must be greater than or equal to Gear 1 and less than or equal to Gear 2. + */ + Q_INVOKABLE void setDriveGear2(float shiftPoint); + + /**jsdoc + * Get the second 'shifting point' for acceleration step function. + * @function MyAvatar.getDriveGear2 + * @returns {number} Value between [0.0, 1.0]. + */ + Q_INVOKABLE float getDriveGear2(); + + /**jsdoc + * Set the third 'shifting point' for acceleration step function. + * @function MyAvatar.setDriveGear3 + * @param {number} shiftPoint - Defines the third shift point for analog movement acceleration step function, between [0, 1]. Must be greater than or equal to Gear 2 and less than or equal to Gear 4. + */ + Q_INVOKABLE void setDriveGear3(float shiftPoint); + + /**jsdoc + * Get the third 'shifting point' for acceleration step function. + * @function MyAvatar.getDriveGear3 + * @returns {number} Value between [0.0, 1.0]. + */ + Q_INVOKABLE float getDriveGear3(); + + /**jsdoc + * Set the fourth 'shifting point' for acceleration step function. + * @function MyAvatar.setDriveGear4 + * @param {number} shiftPoint - Defines the fourth shift point for analog movement acceleration step function, between [0, 1]. Must be greater than Gear 3 and less than Gear 5. + */ + Q_INVOKABLE void setDriveGear4(float shiftPoint); + + /**jsdoc + * Get the fourth 'shifting point' for acceleration step function. + * @function MyAvatar.getDriveGear4 + * @returns {number} Value between [0.0, 1.0]. + */ + Q_INVOKABLE float getDriveGear4(); + + /**jsdoc + * Set the fifth 'shifting point' for acceleration step function. + * @function MyAvatar.setDriveGear5 + * @param {number} shiftPoint - Defines the fifth shift point for analog movement acceleration step function, between [0, 1]. Must be greater than or equal to Gear 4. + */ + Q_INVOKABLE void setDriveGear5(float shiftPoint); + + /**jsdoc + * Get the fifth 'shifting point' for acceleration step function. + * @function MyAvatar.getDriveGear5 + * @returns {number} Value between [0.0, 1.0]. + */ + Q_INVOKABLE float getDriveGear5(); + + /**jsdoc + * Choose the control scheme. + * @function MyAvatar.setControlSchemeIndex + * @param {number} Choose the control scheme to be used. + */ + void setControlSchemeIndex(int index); + + /**jsdoc + * Check what control scheme is in use. + * @function MyAvatar.getControlSchemeIndex + * @returns {number} Returns the index associated with a given control scheme. + */ + int getControlSchemeIndex(); + /**jsdoc * Gets the target scale of the avatar. The target scale is the desired scale of the avatar without any restrictions on * permissible scale values imposed by the domain. @@ -1490,6 +1629,14 @@ public: float getWalkBackwardSpeed() const; void setSprintSpeed(float value); float getSprintSpeed() const; + void setAnalogWalkSpeed(float value); + float getAnalogWalkSpeed() const; + void setAnalogSprintSpeed(float value); + float getAnalogSprintSpeed() const; + void setAnalogPlusWalkSpeed(float value); + float getAnalogPlusWalkSpeed() const; + void setAnalogPlusSprintSpeed(float value); + float getAnalogPlusSprintSpeed() const; void setSitStandStateChange(bool stateChanged); float getSitStandStateChange() const; void updateSitStandState(float newHeightReading, float dt); @@ -2230,6 +2377,13 @@ private: float _boomLength { ZOOM_DEFAULT }; float _yawSpeed; // degrees/sec float _pitchSpeed; // degrees/sec + float _driveGear1 { DEFAULT_GEAR_1 }; + float _driveGear2 { DEFAULT_GEAR_2 }; + float _driveGear3 { DEFAULT_GEAR_3 }; + float _driveGear4 { DEFAULT_GEAR_4 }; + float _driveGear5 { DEFAULT_GEAR_5 }; + int _controlSchemeIndex { CONTROLS_DEFAULT }; + int _movementReference{ 0 }; glm::vec3 _thrust { 0.0f }; // impulse accumulator for outside sources @@ -2270,6 +2424,9 @@ private: // private methods void updateOrientation(float deltaTime); + glm::vec3 calculateScaledDirection(); + float calculateGearedSpeed(const float driveKey); + glm::vec3 scaleMotorSpeed(const glm::vec3 forward, const glm::vec3 right); void updateActionMotor(float deltaTime); void updatePosition(float deltaTime); void updateViewBoom(); @@ -2287,6 +2444,7 @@ private: bool _useSnapTurn { true }; ThreadSafeValueCache _dominantHand { DOMINANT_RIGHT_HAND }; ThreadSafeValueCache _hmdAvatarAlignmentType { DEFAULT_HMD_AVATAR_ALIGNMENT_TYPE }; + ThreadSafeValueCache _strafeEnabled{ DEFAULT_STRAFE_ENABLED }; const float ROLL_CONTROL_DEAD_ZONE_DEFAULT = 8.0f; // degrees const float ROLL_CONTROL_RATE_DEFAULT = 114.0f; // degrees / sec @@ -2438,9 +2596,16 @@ private: ThreadSafeValueCache _lockSitStandState { false }; // max unscaled forward movement speed - ThreadSafeValueCache _walkSpeed { DEFAULT_AVATAR_MAX_WALKING_SPEED }; - ThreadSafeValueCache _walkBackwardSpeed { DEFAULT_AVATAR_MAX_WALKING_BACKWARD_SPEED }; - ThreadSafeValueCache _sprintSpeed { AVATAR_SPRINT_SPEED_SCALAR }; + ThreadSafeValueCache _defaultWalkSpeed { DEFAULT_AVATAR_MAX_WALKING_SPEED }; + ThreadSafeValueCache _defaultWalkBackwardSpeed { DEFAULT_AVATAR_MAX_WALKING_BACKWARD_SPEED }; + ThreadSafeValueCache _defaultSprintSpeed { DEFAULT_AVATAR_MAX_SPRINT_SPEED }; + ThreadSafeValueCache _analogWalkSpeed { ANALOG_AVATAR_MAX_WALKING_SPEED }; + ThreadSafeValueCache _analogWalkBackwardSpeed { ANALOG_AVATAR_MAX_WALKING_BACKWARD_SPEED }; + ThreadSafeValueCache _analogSprintSpeed { ANALOG_AVATAR_MAX_SPRINT_SPEED }; + ThreadSafeValueCache _analogPlusWalkSpeed { ANALOG_PLUS_AVATAR_MAX_WALKING_SPEED }; + ThreadSafeValueCache _analogPlusWalkBackwardSpeed { ANALOG_PLUS_AVATAR_MAX_WALKING_BACKWARD_SPEED }; + ThreadSafeValueCache _analogPlusSprintSpeed { ANALOG_PLUS_AVATAR_MAX_SPRINT_SPEED }; + float _walkSpeedScalar { AVATAR_WALK_SPEED_SCALAR }; bool _isInWalkingState { false }; ThreadSafeValueCache _isInSittingState { false }; @@ -2460,6 +2625,7 @@ private: TimePoint _nextTraitsSendWindow; Setting::Handle _dominantHandSetting; + Setting::Handle _strafeEnabledSetting; Setting::Handle _hmdAvatarAlignmentTypeSetting; Setting::Handle _headPitchSetting; Setting::Handle _scaleSetting; @@ -2473,8 +2639,17 @@ private: Setting::Handle _useSnapTurnSetting; Setting::Handle _userHeightSetting; Setting::Handle _flyingHMDSetting; + Setting::Handle _movementReferenceSetting; Setting::Handle _avatarEntityCountSetting; Setting::Handle _allowTeleportingSetting { "allowTeleporting", true }; + Setting::Handle _driveGear1Setting; + Setting::Handle _driveGear2Setting; + Setting::Handle _driveGear3Setting; + Setting::Handle _driveGear4Setting; + Setting::Handle _driveGear5Setting; + Setting::Handle _analogWalkSpeedSetting; + Setting::Handle _analogPlusWalkSpeedSetting; + Setting::Handle _controlSchemeIndexSetting; std::vector> _avatarEntityIDSettings; std::vector> _avatarEntityDataSettings; Setting::Handle _userRecenterModelSetting; diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index dbd24573ee..75279ef889 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -266,6 +266,11 @@ void setupPreferences() { auto preference = new CheckPreference(VR_MOVEMENT, "Walking", getter, setter); preferences->addPreference(preference); } + { + auto getter = [myAvatar]()->bool { return myAvatar->getStrafeEnabled(); }; + auto setter = [myAvatar](bool value) { myAvatar->setStrafeEnabled(value); }; + preferences->addPreference(new CheckPreference(VR_MOVEMENT, "Strafing", getter, setter)); + } { auto getter = [myAvatar]()->bool { return myAvatar->getFlyingHMDPref(); }; auto setter = [myAvatar](bool value) { myAvatar->setFlyingHMDPref(value); }; @@ -273,6 +278,22 @@ void setupPreferences() { preference->setIndented(true); preferences->addPreference(preference); } + { + auto getter = [myAvatar]()->int { return myAvatar->getMovementReference(); }; + auto setter = [myAvatar](int value) { myAvatar->setMovementReference(value); }; + //auto preference = new CheckPreference(VR_MOVEMENT, "Hand-Relative Movement", getter, setter); + auto preference = new RadioButtonsPreference(VR_MOVEMENT, "Movement Direction", getter, setter); + QStringList items; + items << "HMD-Relative" << "Hand-Relative" << "Hand-Relative (Leveled)"; + preference->setHeading("Movement Direction"); + preference->setItems(items); + preferences->addPreference(preference); + } + { + auto getter = [myAvatar]()->QString { return myAvatar->getDominantHand(); }; + auto setter = [myAvatar](const QString& value) { myAvatar->setDominantHand(value); }; + preferences->addPreference(new PrimaryHandPreference(VR_MOVEMENT, "Dominant Hand", getter, setter)); + } { auto getter = [myAvatar]()->int { return myAvatar->getSnapTurn() ? 0 : 1; }; auto setter = [myAvatar](int value) { myAvatar->setSnapTurn(value == 0); }; @@ -283,6 +304,26 @@ void setupPreferences() { preference->setItems(items); preferences->addPreference(preference); } + { + auto getter = [myAvatar]()->int { return myAvatar->getControlScheme(); }; + auto setter = [myAvatar](int index) { myAvatar->setControlScheme(index); }; + auto preference = new RadioButtonsPreference(VR_MOVEMENT, "Control Scheme", getter, setter); + QStringList items; + items << "Default" << "Analog" << "Analog++"; + preference->setHeading("Control Scheme Selection"); + preference->setItems(items); + preferences->addPreference(preference); + } + { + auto getter = [myAvatar]()->float { return myAvatar->getAnalogPlusWalkSpeed(); }; + auto setter = [myAvatar](float value) { myAvatar->setAnalogPlusWalkSpeed(value); }; + auto preference = new SpinnerSliderPreference(VR_MOVEMENT, "Analog++ Walk Speed", getter, setter); + preference->setMin(6.0f); + preference->setMax(30.0f); + preference->setStep(1); + preference->setDecimals(2); + preferences->addPreference(preference); + } { auto getter = [myAvatar]()->bool { return myAvatar->getShowPlayArea(); }; auto setter = [myAvatar](bool value) { myAvatar->setShowPlayArea(value); }; diff --git a/libraries/shared/src/AvatarConstants.h b/libraries/shared/src/AvatarConstants.h index d55a63b960..5166cb7a0b 100644 --- a/libraries/shared/src/AvatarConstants.h +++ b/libraries/shared/src/AvatarConstants.h @@ -69,7 +69,23 @@ const glm::quat DEFAULT_AVATAR_RIGHTFOOT_ROT { -0.4016716778278351f, 0.915461599 const float DEFAULT_AVATAR_MAX_WALKING_SPEED = 2.6f; // meters / second const float DEFAULT_AVATAR_MAX_WALKING_BACKWARD_SPEED = 2.2f; // meters / second const float DEFAULT_AVATAR_MAX_FLYING_SPEED = 30.0f; // meters / second -const float DEFAULT_AVATAR_WALK_SPEED_THRESHOLD = 0.15f; +const float DEFAULT_AVATAR_MAX_SPRINT_SPEED = 3.4f; // meters / second +const float DEFAULT_AVATAR_WALK_SPEED_THRESHOLD = 0.15f; // meters / second + +const float ANALOG_AVATAR_MAX_WALKING_SPEED = 6.0f; // meters / second +const float ANALOG_AVATAR_MAX_WALKING_BACKWARD_SPEED = 2.2f; // meters / second +const float ANALOG_AVATAR_MAX_FLYING_SPEED = 30.0f; // meters / second +const float ANALOG_AVATAR_MAX_SPRINT_SPEED = 8.0f; // meters / second +const float ANALOG_AVATAR_GEAR_1 = 0.2f; // meters / second +const float ANALOG_AVATAR_GEAR_2 = 0.4f; // meters / second +const float ANALOG_AVATAR_GEAR_3 = 0.6f; // meters / second +const float ANALOG_AVATAR_GEAR_4 = 0.8f; // meters / second +const float ANALOG_AVATAR_GEAR_5 = 1.0f; // meters / second + +const float ANALOG_PLUS_AVATAR_MAX_WALKING_SPEED = 10.0f; // meters / second +const float ANALOG_PLUS_AVATAR_MAX_WALKING_BACKWARD_SPEED = 2.42f; // meters / second +const float ANALOG_PLUS_AVATAR_MAX_FLYING_SPEED = 30.0f; // meters / second +const float ANALOG_PLUS_AVATAR_MAX_SPRINT_SPEED = 20.0f; // meters / second const float DEFAULT_AVATAR_GRAVITY = -5.0f; // meters / second^2 (world) const float DEFAULT_AVATAR_JUMP_SPEED = 3.5f; // meters / second (sensor) @@ -86,6 +102,6 @@ static const float MAX_AVATAR_HEIGHT = 1000.0f * DEFAULT_AVATAR_HEIGHT; // meter static const float MIN_AVATAR_HEIGHT = 0.005f * DEFAULT_AVATAR_HEIGHT; // meters static const float MIN_AVATAR_RADIUS = 0.5f * MIN_AVATAR_HEIGHT; static const float AVATAR_WALK_SPEED_SCALAR = 1.0f; -static const float AVATAR_SPRINT_SPEED_SCALAR = 3.0f; +static const float AVATAR_SPRINT_SPEED_SCALAR = 2.0f; #endif // hifi_AvatarConstants_h diff --git a/scripts/system/controllers/controllerModules/farGrabEntity.js b/scripts/system/controllers/controllerModules/farGrabEntity.js index 65a3671cae..ecafa3cb26 100644 --- a/scripts/system/controllers/controllerModules/farGrabEntity.js +++ b/scripts/system/controllers/controllerModules/farGrabEntity.js @@ -18,7 +18,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); Script.include("/~/system/libraries/controllers.js"); -(function() { +(function () { var MARGIN = 25; function TargetObject(entityID, entityProps) { @@ -27,12 +27,13 @@ Script.include("/~/system/libraries/controllers.js"); this.targetEntityID = null; this.targetEntityProps = null; - this.getTargetEntity = function() { + this.getTargetEntity = function () { var parentPropsLength = this.parentProps.length; if (parentPropsLength !== 0) { var targetEntity = { id: this.parentProps[parentPropsLength - 1].id, - props: this.parentProps[parentPropsLength - 1]}; + props: this.parentProps[parentPropsLength - 1] + }; this.targetEntityID = targetEntity.id; this.targetEntityProps = targetEntity.props; return targetEntity; @@ -41,7 +42,8 @@ Script.include("/~/system/libraries/controllers.js"); this.targetEntityProps = this.entityProps; return { id: this.entityID, - props: this.entityProps}; + props: this.entityProps + }; }; } @@ -62,8 +64,6 @@ Script.include("/~/system/libraries/controllers.js"); this.MIN_HAPTIC_PULSE_INTERVAL = 500; // ms this.disabled = false; var _this = this; - this.leftTrigger = 0.0; - this.rightTrigger = 0.0; this.initialControllerRotation = Quat.IDENTITY; this.currentControllerRotation = Quat.IDENTITY; this.manipulating = false; @@ -101,13 +101,9 @@ Script.include("/~/system/libraries/controllers.js"); return (this.hand === RIGHT_HAND ? LEFT_HAND : RIGHT_HAND); } - this.getOffhandTrigger = function () { - return (_this.hand === RIGHT_HAND ? _this.leftTrigger : _this.rightTrigger); - } - // Activation criteria for rotating a fargrabbed entity. If we're changing the mapping, this is where to do it. - this.shouldManipulateTarget = function () { - return (_this.getOffhandTrigger() > TRIGGER_ON_VALUE) ? true : false; + this.shouldManipulateTarget = function (controllerData) { + return (controllerData.triggerValues[this.getOffhand()] > TRIGGER_ON_VALUE || controllerData.secondaryValues[this.getOffhand()] > TRIGGER_ON_VALUE) ? true : false; }; // Get the delta between the current rotation and where the controller was when manipulation started. @@ -123,11 +119,15 @@ Script.include("/~/system/libraries/controllers.js"); MyAvatar.setJointRotation(FAR_GRAB_JOINTS[this.hand], newTargetRotLocal); }; - this.handToController = function() { + this.setJointRotation = function (newTargetRotLocal) { + MyAvatar.setJointRotation(FAR_GRAB_JOINTS[this.hand], newTargetRotLocal); + }; + + this.handToController = function () { return (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; }; - this.distanceGrabTimescale = function(mass, distance) { + this.distanceGrabTimescale = function (mass, distance) { var timeScale = DISTANCE_HOLDING_ACTION_TIMEFRAME * mass / DISTANCE_HOLDING_UNITY_MASS * distance / DISTANCE_HOLDING_UNITY_DISTANCE; @@ -137,7 +137,7 @@ Script.include("/~/system/libraries/controllers.js"); return timeScale; }; - this.getMass = function(dimensions, density) { + this.getMass = function (dimensions, density) { return (dimensions.x * dimensions.y * dimensions.z) * density; }; @@ -204,8 +204,8 @@ Script.include("/~/system/libraries/controllers.js"); } var farJointIndex = FAR_GRAB_JOINTS[this.hand]; this.grabID = MyAvatar.grab(targetProps.id, farJointIndex, - Entities.worldToLocalPosition(targetProps.position, MyAvatar.SELF_ID, farJointIndex), - Entities.worldToLocalRotation(targetProps.rotation, MyAvatar.SELF_ID, farJointIndex)); + Entities.worldToLocalPosition(targetProps.position, MyAvatar.SELF_ID, farJointIndex), + Entities.worldToLocalRotation(targetProps.rotation, MyAvatar.SELF_ID, farJointIndex)); Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({ action: 'grab', @@ -217,7 +217,7 @@ Script.include("/~/system/libraries/controllers.js"); this.previousRoomControllerPosition = roomControllerPosition; }; - this.continueDistanceHolding = function(controllerData) { + this.continueDistanceHolding = function (controllerData) { var controllerLocation = controllerData.controllerLocations[this.hand]; var worldControllerPosition = controllerLocation.position; var worldControllerRotation = controllerLocation.orientation; @@ -263,7 +263,7 @@ Script.include("/~/system/libraries/controllers.js"); var RADIAL_GRAB_AMPLIFIER = 10.0; if (Math.abs(this.grabRadialVelocity) > 0.0) { this.grabRadius = this.grabRadius + (this.grabRadialVelocity * deltaObjectTime * - this.grabRadius * RADIAL_GRAB_AMPLIFIER); + this.grabRadius * RADIAL_GRAB_AMPLIFIER); } // don't let grabRadius go all the way to zero, because it can't come back from that @@ -278,7 +278,7 @@ Script.include("/~/system/libraries/controllers.js"); var newTargetPosLocal = MyAvatar.worldToJointPoint(newTargetPosition); // This block handles the user's ability to rotate the object they're FarGrabbing - if (this.shouldManipulateTarget()) { + if (this.shouldManipulateTarget(controllerData)) { // Get the pose of the controller that is not grabbing. var pose = Controller.getPoseValue((this.getOffhand() ? Controller.Standard.RightHand : Controller.Standard.LeftHand)); if (pose.valid) { @@ -345,13 +345,13 @@ Script.include("/~/system/libraries/controllers.js"); otherModule.disabled = false; }; - this.updateRecommendedArea = function() { + this.updateRecommendedArea = function () { var dims = Controller.getViewportDimensions(); this.reticleMaxX = dims.x - MARGIN; this.reticleMaxY = dims.y - MARGIN; }; - this.calculateNewReticlePosition = function(intersection) { + this.calculateNewReticlePosition = function (intersection) { this.updateRecommendedArea(); var point2d = HMD.overlayFromWorldPoint(intersection); point2d.x = Math.max(this.reticleMinX, Math.min(point2d.x, this.reticleMaxX)); @@ -359,7 +359,7 @@ Script.include("/~/system/libraries/controllers.js"); return point2d; }; - this.notPointingAtEntity = function(controllerData) { + this.notPointingAtEntity = function (controllerData) { var intersection = controllerData.rayPicks[this.hand]; var entityProperty = Entities.getEntityProperties(intersection.objectID, DISPATCHER_PROPERTIES); var entityType = entityProperty.type; @@ -372,7 +372,7 @@ Script.include("/~/system/libraries/controllers.js"); return false; }; - this.destroyContextOverlay = function(controllerData) { + this.destroyContextOverlay = function (controllerData) { if (this.entityWithContextOverlay) { ContextOverlay.destroyContextOverlay(this.entityWithContextOverlay); this.entityWithContextOverlay = false; @@ -380,7 +380,7 @@ Script.include("/~/system/libraries/controllers.js"); } }; - this.targetIsNull = function() { + this.targetIsNull = function () { var properties = Entities.getEntityProperties(this.targetEntityID, DISPATCHER_PROPERTIES); if (Object.keys(properties).length === 0 && this.distanceHolding) { return true; @@ -424,8 +424,6 @@ Script.include("/~/system/libraries/controllers.js"); }; this.run = function (controllerData) { - this.leftTrigger = controllerData.triggerValues[LEFT_HAND]; - this.rightTrigger = controllerData.triggerValues[RIGHT_HAND]; if (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE || this.targetIsNull()) { this.endFarGrabEntity(controllerData); return makeRunningValues(false, [], []); @@ -522,12 +520,12 @@ Script.include("/~/system/libraries/controllers.js"); _this.contextOverlayTimer && _this.potentialEntityWithContextOverlay === rayPickInfo.objectID) { var cotProps = Entities.getEntityProperties(rayPickInfo.objectID, - DISPATCHER_PROPERTIES); + DISPATCHER_PROPERTIES); var pointerEvent = { type: "Move", id: _this.hand + 1, // 0 is reserved for hardware mouse pos2D: projectOntoEntityXYPlane(rayPickInfo.objectID, - rayPickInfo.intersection, cotProps), + rayPickInfo.intersection, cotProps), pos3D: rayPickInfo.intersection, normal: rayPickInfo.surfaceNormal, direction: Vec3.subtract(ZERO_VEC, rayPickInfo.surfaceNormal), @@ -546,7 +544,7 @@ Script.include("/~/system/libraries/controllers.js"); return this.exitIfDisabled(controllerData); }; - this.exitIfDisabled = function(controllerData) { + this.exitIfDisabled = function (controllerData) { var moduleName = this.hand === RIGHT_HAND ? "RightDisableModules" : "LeftDisableModules"; var disableModule = getEnabledModuleByName(moduleName); if (disableModule) { @@ -563,10 +561,10 @@ Script.include("/~/system/libraries/controllers.js"); return makeRunningValues(true, [], [], laserLockInfo); }; - this.calculateOffset = function(controllerData) { + this.calculateOffset = function (controllerData) { if (this.distanceHolding) { var targetProps = Entities.getEntityProperties(this.targetObject.entityID, - [ "position", "rotation", "registrationPoint", "dimensions" ]); + ["position", "rotation", "registrationPoint", "dimensions"]); return worldPositionToRegistrationFrameMatrix(targetProps, controllerData.rayPicks[this.hand].intersection); } return undefined; diff --git a/scripts/system/controllers/controllerModules/teleport.js b/scripts/system/controllers/controllerModules/teleport.js index 23457cdd85..5a51773930 100644 --- a/scripts/system/controllers/controllerModules/teleport.js +++ b/scripts/system/controllers/controllerModules/teleport.js @@ -129,6 +129,8 @@ Script.include("/~/system/libraries/controllers.js"); this.init = false; this.hand = hand; this.buttonValue = 0; + this.standardAxisLY = 0.0; + this.standardAxisRY = 0.0; this.disabled = false; // used by the 'Hifi-Teleport-Disabler' message handler this.active = false; this.state = TELEPORTER_STATES.IDLE; @@ -690,6 +692,44 @@ Script.include("/~/system/libraries/controllers.js"); } }; + this.getStandardLY = function (value) { + _this.standardAxisLY = value; + }; + + this.getStandardRY = function (value) { + _this.standardAxisRY = value; + }; + + // Return value for the getDominantY and getOffhandY functions has to be inverted. + this.getDominantY = function () { + return (MyAvatar.getDominantHand() === "left") ? -(_this.standardAxisLY) : -(_this.standardAxisRY); + }; + + this.getOffhandY = function () { + return (MyAvatar.getDominantHand() === "left") ? -(_this.standardAxisRY) : -(_this.standardAxisLY); + }; + + this.getDominantHand = function () { + return (MyAvatar.getDominantHand() === "left") ? LEFT_HAND : RIGHT_HAND; + } + + this.getOffHand = function () { + return (MyAvatar.getDominantHand() === "left") ? RIGHT_HAND : LEFT_HAND; + } + + this.showReticle = function () { + return (_this.getDominantY() > TELEPORT_DEADZONE) ? true : false; + }; + + this.shouldTeleport = function () { + return (_this.getDominantY() > TELEPORT_DEADZONE && _this.getOffhandY() > TELEPORT_DEADZONE) ? true : false; + }; + + this.shouldCancel = function () { + //return (_this.getDominantY() < -TELEPORT_DEADZONE || _this.getOffhandY() < -TELEPORT_DEADZONE) ? true : false; + return (_this.getDominantY() <= TELEPORT_DEADZONE) ? true : false; + }; + this.parameters = makeDispatcherModuleParameters( 80, this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"], @@ -706,7 +746,7 @@ Script.include("/~/system/libraries/controllers.js"); } var otherModule = this.getOtherModule(); - if (!this.disabled && this.buttonValue !== 0 && !otherModule.active) { + if (!this.disabled && this.showReticle() && !otherModule.active && this.hand === this.getDominantHand()) { this.active = true; this.enterTeleport(); return makeRunningValues(true, [], []); @@ -715,6 +755,12 @@ Script.include("/~/system/libraries/controllers.js"); }; this.run = function(controllerData, deltaTime) { + // Kill condition: + if (_this.shouldCancel()) { + _this.disableLasers(); + this.active = false; + return makeRunningValues(false, [], []); + } // Get current hand pose information to see if the pose is valid var pose = Controller.getPoseValue(handInfo[(_this.hand === RIGHT_HAND) ? 'right' : 'left'].controllerInput); @@ -778,7 +824,7 @@ Script.include("/~/system/libraries/controllers.js"); this.teleport = function(newResult, target) { var result = newResult; _this.teleportedPosition = newResult.intersection; - if (_this.buttonValue !== 0) { + if (!_this.shouldTeleport()) { return makeRunningValues(true, [], []); } @@ -801,8 +847,8 @@ Script.include("/~/system/libraries/controllers.js"); }; this.disableLasers = function() { - _this.setPlayAreaVisible(false, null, true); - _this.setTeleportVisible(false, null, true); + _this.setPlayAreaVisible(false, null, false); + _this.setTeleportVisible(false, null, false); Pointers.disablePointer(_this.teleportParabolaHandVisuals); Pointers.disablePointer(_this.teleportParabolaHandCollisions); Pointers.disablePointer(_this.teleportParabolaHeadVisuals); @@ -982,6 +1028,10 @@ Script.include("/~/system/libraries/controllers.js"); // Teleport actions. teleportMapping.from(Controller.Standard.LeftPrimaryThumb).peek().to(leftTeleporter.buttonPress); teleportMapping.from(Controller.Standard.RightPrimaryThumb).peek().to(rightTeleporter.buttonPress); + teleportMapping.from(Controller.Standard.LY).peek().to(leftTeleporter.getStandardLY); + teleportMapping.from(Controller.Standard.RY).peek().to(leftTeleporter.getStandardRY); + teleportMapping.from(Controller.Standard.LY).peek().to(rightTeleporter.getStandardLY); + teleportMapping.from(Controller.Standard.RY).peek().to(rightTeleporter.getStandardRY); } var leftTeleporter = new Teleporter(LEFT_HAND); diff --git a/scripts/system/controllers/controllerScripts.js b/scripts/system/controllers/controllerScripts.js index ca7d041792..c9cb61b5f5 100644 --- a/scripts/system/controllers/controllerScripts.js +++ b/scripts/system/controllers/controllerScripts.js @@ -15,7 +15,7 @@ var CONTOLLER_SCRIPTS = [ "squeezeHands.js", "controllerDisplayManager.js", "grab.js", - "toggleAdvancedMovementForHandControllers.js", + //"toggleAdvancedMovementForHandControllers.js", "handTouch.js", "controllerDispatcher.js", "controllerModules/nearParentGrabOverlay.js", diff --git a/scripts/system/libraries/controllerDispatcherUtils.js b/scripts/system/libraries/controllerDispatcherUtils.js index 51645e5502..3b81e17473 100644 --- a/scripts/system/libraries/controllerDispatcherUtils.js +++ b/scripts/system/libraries/controllerDispatcherUtils.js @@ -103,6 +103,8 @@ TEAR_AWAY_DISTANCE = 0.15; // ungrab an entity if its bounding-box moves this fa TEAR_AWAY_COUNT = 2; // multiply by TEAR_AWAY_CHECK_TIME to know how long the item must be away TEAR_AWAY_CHECK_TIME = 0.15; // seconds, duration between checks +TELEPORT_DEADZONE = 0.15; + NEAR_GRAB_DISTANCE = 0.14; // Grab an entity if its bounding box is within this distance. // Smaller than TEAR_AWAY_DISTANCE for hysteresis. diff --git a/scripts/system/tablet-ui/tabletUI.js b/scripts/system/tablet-ui/tabletUI.js index 60848224bb..a38febaa77 100644 --- a/scripts/system/tablet-ui/tabletUI.js +++ b/scripts/system/tablet-ui/tabletUI.js @@ -291,7 +291,7 @@ var clickMapping = Controller.newMapping('tabletToggle-click'); var wantsMenu = 0; clickMapping.from(function () { return wantsMenu; }).to(Controller.Actions.ContextMenu); - clickMapping.from(Controller.Standard.RightSecondaryThumb).peek().to(function (clicked) { + clickMapping.from(Controller.Standard.RightSecondaryThumb).peek().when(Controller.Hardware.Application.LeftHandDominant).to(function (clicked) { if (clicked) { //activeHudPoint2d(Controller.Standard.RightHand); Messages.sendLocalMessage("toggleHand", Controller.Standard.RightHand); @@ -299,7 +299,7 @@ wantsMenu = clicked; }); - clickMapping.from(Controller.Standard.LeftSecondaryThumb).peek().to(function (clicked) { + clickMapping.from(Controller.Standard.LeftSecondaryThumb).peek().when(Controller.Hardware.Application.RightHandDominant).to(function (clicked) { if (clicked) { //activeHudPoint2d(Controller.Standard.LeftHand); Messages.sendLocalMessage("toggleHand", Controller.Standard.LeftHand);