Previously the HUD fading in/out would also recenter the hmd sensor and the avatar, which caused many problems including: * The user's view could shift vertically. * Your avatar would briefly go into t-pose * other users would see your avatar go into t-pose. Now we now move the UI sphere instead, which results in a much smoother experience. MyAvatar: added hasDriveInput method. OverlayConductor: * removed avatar and sensor reset, instead the overlay's modelTransform is changed. * revived STANDING mode, which is active if myAvatar->getClearOverlayWhenDriving() is true and you are wearing an HMD. * SITTING & FLAT mode should be unchanged. * Instead of using avatar velocity to fade out/fade in the hud, We use the presense or absanse of avatar drive input. * Additionally, we check distance to the UI sphere, and quickly recenter the hud if the users head is too close to the actual hud sphere. CompositorHelper: * Bug fixes for ray picks not using the modelTransform.
Showing the changes to interface/src/ui/OverlayConductor.cpp (1 of 6 files). Show all
Unified · Split
| @@ -22,10 +22,31 @@ OverlayConductor::OverlayConductor() { · 21 unmodified lines | |||
| 22 | OverlayConductor::~OverlayConductor() { | 22 | OverlayConductor::~OverlayConductor() { |
| 23 | } | 23 | } |
| 24 | 24 | ||
| 25 | bool OverlayConductor::shouldCenterUI() const { | ||
| 26 | |||
| 27 | glm::mat4 hmdMat = qApp->getHMDSensorPose(); | ||
| 28 | glm::vec3 hmdPos = extractTranslation(hmdMat); | ||
| 29 | glm::vec3 hmdForward = transformVectorFast(hmdMat, glm::vec3(0.0f, 0.0f, -1.0f)); | ||
| 30 | |||
| 31 | Transform uiTransform = qApp->getApplicationCompositor().getModelTransform(); | ||
| 32 | glm::vec3 uiPos = uiTransform.getTranslation(); | ||
| 33 | glm::vec3 uiForward = uiTransform.getRotation() * glm::vec3(0.0f, 0.0f, -1.0f); | ||
| 34 | |||
| 35 | const float MAX_COMPOSITOR_DISTANCE = 0.6f; | ||
| 36 | const float MAX_COMPOSITOR_ANGLE = 180.0f; // rotation check is effectively disabled | ||
| 37 | if (glm::distance(uiPos, hmdPos) > MAX_COMPOSITOR_DISTANCE || | ||
| 38 | glm::dot(uiForward, hmdForward) < cosf(glm::radians(MAX_COMPOSITOR_ANGLE))) { | ||
| 39 | return true; | ||
| 40 | } | ||
| 41 | return false; | ||
| 42 | } | ||
| 43 | |||
| 25 | void OverlayConductor::update(float dt) { | 44 | void OverlayConductor::update(float dt) { |
| 26 | 45 | ||
| 27 | updateMode(); | 46 | updateMode(); |
| 28 | 47 | ||
| 48 | MyAvatar* myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar(); | ||
| 49 | |||
| 29 | switch (_mode) { | 50 | switch (_mode) { |
| 30 | case SITTING: { | 51 | case SITTING: { |
| 31 | // when sitting, the overlay is at the origin, facing down the -z axis. | 52 | // when sitting, the overlay is at the origin, facing down the -z axis. |
| @@ -36,75 +57,49 @@ void OverlayConductor::update(float dt) { · 4 unmodified lines | |||
| 36 | break; | 57 | break; |
| 37 | } | 58 | } |
| 38 | case STANDING: { | 59 | case STANDING: { |
| 39 | // when standing, the overlay is at a reference position, which is set when the overlay is | ||
| 40 | // enabled. The camera is taken directly from the HMD, but in world space. | ||
| 41 | // So the sensorToWorldMatrix must be applied. | ||
| 42 | MyAvatar* myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar(); | ||
| 43 | Transform t; | ||
| 44 | t.evalFromRawMatrix(myAvatar->getSensorToWorldMatrix()); | ||
| 45 | qApp->getApplicationCompositor().setCameraBaseTransform(t); | ||
| 46 | |||
| 47 | // detect when head moves out side of sweet spot, or looks away. | ||
| 48 | mat4 headMat = myAvatar->getSensorToWorldMatrix() * qApp->getHMDSensorPose(); | ||
| 49 | vec3 headWorldPos = extractTranslation(headMat); | ||
| 50 | vec3 headForward = glm::quat_cast(headMat) * glm::vec3(0.0f, 0.0f, -1.0f); | ||
| 51 | Transform modelXform = qApp->getApplicationCompositor().getModelTransform(); | ||
| 52 | vec3 compositorWorldPos = modelXform.getTranslation(); | ||
| 53 | vec3 compositorForward = modelXform.getRotation() * glm::vec3(0.0f, 0.0f, -1.0f); | ||
| 54 | const float MAX_COMPOSITOR_DISTANCE = 0.6f; | ||
| 55 | const float MAX_COMPOSITOR_ANGLE = 110.0f; | ||
| 56 | if (_enabled && (glm::distance(headWorldPos, compositorWorldPos) > MAX_COMPOSITOR_DISTANCE || | ||
| 57 | glm::dot(headForward, compositorForward) < cosf(glm::radians(MAX_COMPOSITOR_ANGLE)))) { | ||
| 58 | // fade out the overlay | ||
| 59 | setEnabled(false); | ||
| 60 | } | ||
| 61 | break; | ||
| 62 | } | ||
| 63 | case FLAT: | ||
| 64 | // do nothing | ||
| 65 | break; | ||
| 66 | } | ||
| 67 | } | ||
| 68 | 60 | ||
| 69 | void OverlayConductor::updateMode() { | ||
| 70 | MyAvatar* myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar(); | ||
| 71 | if (myAvatar->getClearOverlayWhenDriving()) { | ||
| 72 | float speed = glm::length(myAvatar->getVelocity()); | ||
| 73 | const float MIN_DRIVING = 0.2f; | ||
| 74 | const float MAX_NOT_DRIVING = 0.01f; | ||
| 75 | const quint64 REQUIRED_USECS_IN_NEW_MODE_BEFORE_INVISIBLE = 200 * 1000; | 61 | const quint64 REQUIRED_USECS_IN_NEW_MODE_BEFORE_INVISIBLE = 200 * 1000; |
| 76 | const quint64 REQUIRED_USECS_IN_NEW_MODE_BEFORE_VISIBLE = 1000 * 1000; | 62 | const quint64 REQUIRED_USECS_IN_NEW_MODE_BEFORE_VISIBLE = 1000 * 1000; |
| 77 | bool nowDriving = _driving; // Assume current _driving mode unless... | 63 | |
| 78 | if (speed > MIN_DRIVING) { // ... we're definitely moving... | 64 | // fade in or out the overlay, based on driving. |
| 79 | nowDriving = true; | 65 | bool nowDriving = myAvatar->hasDriveInput(); |
| 80 | } else if (speed < MAX_NOT_DRIVING) { // ... or definitely not. | ||
| 81 | nowDriving = false; | ||
| 82 | } | ||
| 83 | // Check that we're in this new mode for long enough to really trigger a transition. | 66 | // Check that we're in this new mode for long enough to really trigger a transition. |
| 84 | if (nowDriving == _driving) { // If there's no change in state, clear any attepted timer. | 67 | if (nowDriving == _driving) { // If there's no change in state, clear any attepted timer. |
| 85 | _timeInPotentialMode = 0; | 68 | _timeInPotentialMode = 0; |
| 86 | } else if (_timeInPotentialMode == 0) { // We've just changed with no timer, so start timing now. | 69 | } else if (_timeInPotentialMode == 0) { // We've just changed with no timer, so start timing now. |
| 87 | _timeInPotentialMode = usecTimestampNow(); | 70 | _timeInPotentialMode = usecTimestampNow(); |
| 88 | } else if ((usecTimestampNow() - _timeInPotentialMode) > (nowDriving ? REQUIRED_USECS_IN_NEW_MODE_BEFORE_INVISIBLE : REQUIRED_USECS_IN_NEW_MODE_BEFORE_VISIBLE)) { | 71 | } else if ((usecTimestampNow() - _timeInPotentialMode) > (nowDriving ? REQUIRED_USECS_IN_NEW_MODE_BEFORE_INVISIBLE : REQUIRED_USECS_IN_NEW_MODE_BEFORE_VISIBLE)) { |
| 89 | _timeInPotentialMode = 0; // a real transition | 72 | _timeInPotentialMode = 0; // a real transition |
| 90 | if (nowDriving) { | 73 | bool wantsOverlays = Menu::getInstance()->isOptionChecked(MenuOption::Overlays); |
| 91 | _wantsOverlays = Menu::getInstance()->isOptionChecked(MenuOption::Overlays); | 74 | if (wantsOverlays) { |
| 92 | } else { // reset when coming out of driving | ||
| 93 | _mode = FLAT; // Seems appropriate to let things reset, below, after the following. | ||
| 94 | // All reset of, e.g., room-scale location as though by apostrophe key, without all the other adjustments. | ||
| 95 | qApp->getActiveDisplayPlugin()->resetSensors(); | ||
| 96 | myAvatar->reset(true, false, false); | ||
| 97 | } | ||
| 98 | if (_wantsOverlays) { | ||
| 99 | setEnabled(!nowDriving); | 75 | setEnabled(!nowDriving); |
| 100 | } | 76 | } |
| 101 | _driving = nowDriving; | 77 | _driving = nowDriving; |
| 102 | } // Else haven't accumulated enough time in new mode, but keep timing. | 78 | } |
| 79 | |||
| 80 | // center the UI | ||
| 81 | if (shouldCenterUI()) { | ||
| 82 | Transform hmdTransform(cancelOutRollAndPitch(qApp->getHMDSensorPose())); | ||
| 83 | qApp->getApplicationCompositor().setModelTransform(hmdTransform); | ||
| 84 | } | ||
| 85 | break; | ||
| 86 | } | ||
| 87 | case FLAT: | ||
| 88 | // do nothing | ||
| 89 | break; | ||
| 103 | } | 90 | } |
| 91 | } | ||
| 92 | |||
| 93 | void OverlayConductor::updateMode() { | ||
| 94 | MyAvatar* myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar(); | ||
| 104 | 95 | ||
| 105 | Mode newMode; | 96 | Mode newMode; |
| 106 | if (qApp->isHMDMode()) { | 97 | if (qApp->isHMDMode()) { |
| 107 | newMode = SITTING; | 98 | if (myAvatar->getClearOverlayWhenDriving()) { |
| 99 | newMode = STANDING; | ||
| 100 | } else { | ||
| 101 | newMode = SITTING; | ||
| 102 | } | ||
| 108 | } else { | 103 | } else { |
| 109 | newMode = FLAT; | 104 | newMode = FLAT; |
| 110 | } | 105 | } |
| @@ -117,11 +112,10 @@ void OverlayConductor::updateMode() { · 6 unmodified lines | |||
| 117 | qApp->getApplicationCompositor().setModelTransform(Transform()); | 112 | qApp->getApplicationCompositor().setModelTransform(Transform()); |
| 118 | break; | 113 | break; |
| 119 | } | 114 | } |
| 120 | case STANDING: { // STANDING mode is not currently used. | 115 | case STANDING: { |
| 121 | // enter the STANDING state | 116 | // enter the STANDING state |
| 122 | // place the overlay at the current hmd position in world space | 117 | Transform hmdTransform(cancelOutRollAndPitch(qApp->getHMDSensorPose())); |
| 123 | auto camMat = cancelOutRollAndPitch(myAvatar->getSensorToWorldMatrix() * qApp->getHMDSensorPose()); | 118 | qApp->getApplicationCompositor().setModelTransform(hmdTransform); |
| 124 | qApp->getApplicationCompositor().setModelTransform(Transform(camMat)); | ||
| 125 | break; | 119 | break; |
| 126 | } | 120 | } |
| 127 | 121 | ||
| @@ -132,7 +126,6 @@ void OverlayConductor::updateMode() { · 4 unmodified lines | |||
| 132 | } | 126 | } |
| 133 | 127 | ||
| 134 | _mode = newMode; | 128 | _mode = newMode; |
| 135 | |||
| 136 | } | 129 | } |
| 137 | 130 | ||
| 138 | void OverlayConductor::setEnabled(bool enabled) { | 131 | void OverlayConductor::setEnabled(bool enabled) { |
| @@ -149,7 +142,7 @@ void OverlayConductor::setEnabled(bool enabled) { · 10 unmodified lines | |||
| 149 | MyAvatar* myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar(); | 142 | MyAvatar* myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar(); |
| 150 | auto camMat = cancelOutRollAndPitch(myAvatar->getSensorToWorldMatrix() * qApp->getHMDSensorPose()); | 143 | auto camMat = cancelOutRollAndPitch(myAvatar->getSensorToWorldMatrix() * qApp->getHMDSensorPose()); |
| 151 | qApp->getApplicationCompositor().setModelTransform(Transform(camMat)); | 144 | qApp->getApplicationCompositor().setModelTransform(Transform(camMat)); |
| 152 | } | 145 | } |
| 153 | } | 146 | } |
| 154 | 147 | ||
| 155 | bool OverlayConductor::getEnabled() const { | 148 | bool OverlayConductor::getEnabled() const { |