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 | 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 | 44 | void OverlayConductor::update(float dt) { |
| 26 | 45 | |
| 27 | 46 | updateMode(); |
| 28 | 47 | |
| 48 | + MyAvatar* myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar(); | |
| 49 | + | |
| 29 | 50 | switch (_mode) { |
| 30 | 51 | case SITTING: { |
| 31 | 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 | 57 | break; |
| 37 | 58 | } |
| 38 | 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 | 61 | const quint64 REQUIRED_USECS_IN_NEW_MODE_BEFORE_INVISIBLE = 200 * 1000; |
| 76 | 62 | const quint64 REQUIRED_USECS_IN_NEW_MODE_BEFORE_VISIBLE = 1000 * 1000; |
| 77 | − bool nowDriving = _driving; // Assume current _driving mode unless... | |
| 78 | − if (speed > MIN_DRIVING) { // ... we're definitely moving... | |
| 79 | − nowDriving = true; | |
| 80 | − } else if (speed < MAX_NOT_DRIVING) { // ... or definitely not. | |
| 81 | − nowDriving = false; | |
| 82 | − } | |
| 63 | + | |
| 64 | + // fade in or out the overlay, based on driving. | |
| 65 | + bool nowDriving = myAvatar->hasDriveInput(); | |
| 83 | 66 | // Check that we're in this new mode for long enough to really trigger a transition. |
| 84 | 67 | if (nowDriving == _driving) { // If there's no change in state, clear any attepted timer. |
| 85 | 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 | 70 | _timeInPotentialMode = usecTimestampNow(); |
| 88 | 71 | } else if ((usecTimestampNow() - _timeInPotentialMode) > (nowDriving ? REQUIRED_USECS_IN_NEW_MODE_BEFORE_INVISIBLE : REQUIRED_USECS_IN_NEW_MODE_BEFORE_VISIBLE)) { |
| 89 | 72 | _timeInPotentialMode = 0; // a real transition |
| 90 | − if (nowDriving) { | |
| 91 | − _wantsOverlays = Menu::getInstance()->isOptionChecked(MenuOption::Overlays); | |
| 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) { | |
| 73 | + bool wantsOverlays = Menu::getInstance()->isOptionChecked(MenuOption::Overlays); | |
| 74 | + if (wantsOverlays) { | |
| 99 | 75 | setEnabled(!nowDriving); |
| 100 | 76 | } |
| 101 | 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 | 96 | Mode newMode; |
| 106 | 97 | if (qApp->isHMDMode()) { |
| 107 | − newMode = SITTING; | |
| 98 | + if (myAvatar->getClearOverlayWhenDriving()) { | |
| 99 | + newMode = STANDING; | |
| 100 | + } else { | |
| 101 | + newMode = SITTING; | |
| 102 | + } | |
| 108 | 103 | } else { |
| 109 | 104 | newMode = FLAT; |
| 110 | 105 | } |
| @@ -117,11 +112,10 @@ void OverlayConductor::updateMode() { · 6 unmodified lines | ||
| 117 | 112 | qApp->getApplicationCompositor().setModelTransform(Transform()); |
| 118 | 113 | break; |
| 119 | 114 | } |
| 120 | − case STANDING: { // STANDING mode is not currently used. | |
| 115 | + case STANDING: { | |
| 121 | 116 | // enter the STANDING state |
| 122 | − // place the overlay at the current hmd position in world space | |
| 123 | − auto camMat = cancelOutRollAndPitch(myAvatar->getSensorToWorldMatrix() * qApp->getHMDSensorPose()); | |
| 124 | − qApp->getApplicationCompositor().setModelTransform(Transform(camMat)); | |
| 117 | + Transform hmdTransform(cancelOutRollAndPitch(qApp->getHMDSensorPose())); | |
| 118 | + qApp->getApplicationCompositor().setModelTransform(hmdTransform); | |
| 125 | 119 | break; |
| 126 | 120 | } |
| 127 | 121 | |
| @@ -132,7 +126,6 @@ void OverlayConductor::updateMode() { · 4 unmodified lines | ||
| 132 | 126 | } |
| 133 | 127 | |
| 134 | 128 | _mode = newMode; |
| 135 | − | |
| 136 | 129 | } |
| 137 | 130 | |
| 138 | 131 | void OverlayConductor::setEnabled(bool enabled) { |
| @@ -149,7 +142,7 @@ void OverlayConductor::setEnabled(bool enabled) { · 10 unmodified lines | ||
| 149 | 142 | MyAvatar* myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar(); |
| 150 | 143 | auto camMat = cancelOutRollAndPitch(myAvatar->getSensorToWorldMatrix() * qApp->getHMDSensorPose()); |
| 151 | 144 | qApp->getApplicationCompositor().setModelTransform(Transform(camMat)); |
| 152 | − } | |
| 145 | + } | |
| 153 | 146 | } |
| 154 | 147 | |
| 155 | 148 | bool OverlayConductor::getEnabled() const { |