From 5ef6847dc35b047ffbced088d07de9bcc79bda8e Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Wed, 8 Jun 2016 18:36:36 -0700 Subject: [PATCH 1/7] HMD re-centering while driving improvements. 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. HmdDisplayPlugin: * Bug fixes for rendering not using the modelTransform. --- interface/src/avatar/MyAvatar.cpp | 7 +- interface/src/avatar/MyAvatar.h | 2 + interface/src/ui/OverlayConductor.cpp | 113 ++++++++---------- interface/src/ui/OverlayConductor.h | 1 + .../src/display-plugins/CompositorHelper.cpp | 4 +- .../display-plugins/hmd/HmdDisplayPlugin.cpp | 3 +- 6 files changed, 66 insertions(+), 64 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 62772c6933..76e48d34d6 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1261,8 +1261,7 @@ void MyAvatar::prepareForPhysicsSimulation() { _characterController.setPositionAndOrientation(getPosition(), getOrientation()); if (qApp->isHMDMode()) { - bool hasDriveInput = fabsf(_driveKeys[TRANSLATE_X]) > 0.0f || fabsf(_driveKeys[TRANSLATE_Z]) > 0.0f; - _follow.prePhysicsUpdate(*this, deriveBodyFromHMDSensor(), _bodySensorMatrix, hasDriveInput); + _follow.prePhysicsUpdate(*this, deriveBodyFromHMDSensor(), _bodySensorMatrix, hasDriveInput()); } else { _follow.deactivate(); } @@ -2135,3 +2134,7 @@ bool MyAvatar::didTeleport() { lastPosition = pos; return (changeInPosition.length() > MAX_AVATAR_MOVEMENT_PER_FRAME); } + +bool MyAvatar::hasDriveInput() const { + return fabsf(_driveKeys[TRANSLATE_X]) > 0.0f || fabsf(_driveKeys[TRANSLATE_Z]) > 0.0f; +} diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index d3da32e0ed..381d658046 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -264,6 +264,8 @@ public: controller::Pose getLeftHandControllerPoseInAvatarFrame() const; controller::Pose getRightHandControllerPoseInAvatarFrame() const; + bool hasDriveInput() const; + public slots: void increaseSize(); void decreaseSize(); diff --git a/interface/src/ui/OverlayConductor.cpp b/interface/src/ui/OverlayConductor.cpp index 54dba229e3..c4fd938e97 100644 --- a/interface/src/ui/OverlayConductor.cpp +++ b/interface/src/ui/OverlayConductor.cpp @@ -22,10 +22,31 @@ OverlayConductor::OverlayConductor() { OverlayConductor::~OverlayConductor() { } +bool OverlayConductor::shouldCenterUI() const { + + glm::mat4 hmdMat = qApp->getHMDSensorPose(); + glm::vec3 hmdPos = extractTranslation(hmdMat); + glm::vec3 hmdForward = transformVectorFast(hmdMat, glm::vec3(0.0f, 0.0f, -1.0f)); + + Transform uiTransform = qApp->getApplicationCompositor().getModelTransform(); + glm::vec3 uiPos = uiTransform.getTranslation(); + glm::vec3 uiForward = uiTransform.getRotation() * glm::vec3(0.0f, 0.0f, -1.0f); + + const float MAX_COMPOSITOR_DISTANCE = 0.6f; + const float MAX_COMPOSITOR_ANGLE = 180.0f; // rotation check is effectively disabled + if (glm::distance(uiPos, hmdPos) > MAX_COMPOSITOR_DISTANCE || + glm::dot(uiForward, hmdForward) < cosf(glm::radians(MAX_COMPOSITOR_ANGLE))) { + return true; + } + return false; +} + void OverlayConductor::update(float dt) { updateMode(); + MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); + switch (_mode) { case SITTING: { // when sitting, the overlay is at the origin, facing down the -z axis. @@ -36,27 +57,30 @@ void OverlayConductor::update(float dt) { break; } case STANDING: { - // when standing, the overlay is at a reference position, which is set when the overlay is - // enabled. The camera is taken directly from the HMD, but in world space. - // So the sensorToWorldMatrix must be applied. - MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); - Transform t; - t.evalFromRawMatrix(myAvatar->getSensorToWorldMatrix()); - qApp->getApplicationCompositor().setCameraBaseTransform(t); - // detect when head moves out side of sweet spot, or looks away. - mat4 headMat = myAvatar->getSensorToWorldMatrix() * qApp->getHMDSensorPose(); - vec3 headWorldPos = extractTranslation(headMat); - vec3 headForward = glm::quat_cast(headMat) * glm::vec3(0.0f, 0.0f, -1.0f); - Transform modelXform = qApp->getApplicationCompositor().getModelTransform(); - vec3 compositorWorldPos = modelXform.getTranslation(); - vec3 compositorForward = modelXform.getRotation() * glm::vec3(0.0f, 0.0f, -1.0f); - const float MAX_COMPOSITOR_DISTANCE = 0.6f; - const float MAX_COMPOSITOR_ANGLE = 110.0f; - if (_enabled && (glm::distance(headWorldPos, compositorWorldPos) > MAX_COMPOSITOR_DISTANCE || - glm::dot(headForward, compositorForward) < cosf(glm::radians(MAX_COMPOSITOR_ANGLE)))) { - // fade out the overlay - setEnabled(false); + const quint64 REQUIRED_USECS_IN_NEW_MODE_BEFORE_INVISIBLE = 200 * 1000; + const quint64 REQUIRED_USECS_IN_NEW_MODE_BEFORE_VISIBLE = 1000 * 1000; + + // fade in or out the overlay, based on driving. + bool nowDriving = myAvatar->hasDriveInput(); + // Check that we're in this new mode for long enough to really trigger a transition. + if (nowDriving == _driving) { // If there's no change in state, clear any attepted timer. + _timeInPotentialMode = 0; + } else if (_timeInPotentialMode == 0) { // We've just changed with no timer, so start timing now. + _timeInPotentialMode = usecTimestampNow(); + } else if ((usecTimestampNow() - _timeInPotentialMode) > (nowDriving ? REQUIRED_USECS_IN_NEW_MODE_BEFORE_INVISIBLE : REQUIRED_USECS_IN_NEW_MODE_BEFORE_VISIBLE)) { + _timeInPotentialMode = 0; // a real transition + bool wantsOverlays = Menu::getInstance()->isOptionChecked(MenuOption::Overlays); + if (wantsOverlays) { + setEnabled(!nowDriving); + } + _driving = nowDriving; + } + + // center the UI + if (shouldCenterUI()) { + Transform hmdTransform(cancelOutRollAndPitch(qApp->getHMDSensorPose())); + qApp->getApplicationCompositor().setModelTransform(hmdTransform); } break; } @@ -68,43 +92,14 @@ void OverlayConductor::update(float dt) { void OverlayConductor::updateMode() { MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); - if (myAvatar->getClearOverlayWhenDriving()) { - float speed = glm::length(myAvatar->getVelocity()); - const float MIN_DRIVING = 0.2f; - const float MAX_NOT_DRIVING = 0.01f; - const quint64 REQUIRED_USECS_IN_NEW_MODE_BEFORE_INVISIBLE = 200 * 1000; - const quint64 REQUIRED_USECS_IN_NEW_MODE_BEFORE_VISIBLE = 1000 * 1000; - bool nowDriving = _driving; // Assume current _driving mode unless... - if (speed > MIN_DRIVING) { // ... we're definitely moving... - nowDriving = true; - } else if (speed < MAX_NOT_DRIVING) { // ... or definitely not. - nowDriving = false; - } - // Check that we're in this new mode for long enough to really trigger a transition. - if (nowDriving == _driving) { // If there's no change in state, clear any attepted timer. - _timeInPotentialMode = 0; - } else if (_timeInPotentialMode == 0) { // We've just changed with no timer, so start timing now. - _timeInPotentialMode = usecTimestampNow(); - } else if ((usecTimestampNow() - _timeInPotentialMode) > (nowDriving ? REQUIRED_USECS_IN_NEW_MODE_BEFORE_INVISIBLE : REQUIRED_USECS_IN_NEW_MODE_BEFORE_VISIBLE)) { - _timeInPotentialMode = 0; // a real transition - if (nowDriving) { - _wantsOverlays = Menu::getInstance()->isOptionChecked(MenuOption::Overlays); - } else { // reset when coming out of driving - _mode = FLAT; // Seems appropriate to let things reset, below, after the following. - // All reset of, e.g., room-scale location as though by apostrophe key, without all the other adjustments. - qApp->getActiveDisplayPlugin()->resetSensors(); - myAvatar->reset(true, false, false); - } - if (_wantsOverlays) { - setEnabled(!nowDriving); - } - _driving = nowDriving; - } // Else haven't accumulated enough time in new mode, but keep timing. - } Mode newMode; if (qApp->isHMDMode()) { - newMode = SITTING; + if (myAvatar->getClearOverlayWhenDriving()) { + newMode = STANDING; + } else { + newMode = SITTING; + } } else { newMode = FLAT; } @@ -117,11 +112,10 @@ void OverlayConductor::updateMode() { qApp->getApplicationCompositor().setModelTransform(Transform()); break; } - case STANDING: { // STANDING mode is not currently used. + case STANDING: { // enter the STANDING state - // place the overlay at the current hmd position in world space - auto camMat = cancelOutRollAndPitch(myAvatar->getSensorToWorldMatrix() * qApp->getHMDSensorPose()); - qApp->getApplicationCompositor().setModelTransform(Transform(camMat)); + Transform hmdTransform(cancelOutRollAndPitch(qApp->getHMDSensorPose())); + qApp->getApplicationCompositor().setModelTransform(hmdTransform); break; } @@ -132,7 +126,6 @@ void OverlayConductor::updateMode() { } _mode = newMode; - } void OverlayConductor::setEnabled(bool enabled) { @@ -149,7 +142,7 @@ void OverlayConductor::setEnabled(bool enabled) { MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); auto camMat = cancelOutRollAndPitch(myAvatar->getSensorToWorldMatrix() * qApp->getHMDSensorPose()); qApp->getApplicationCompositor().setModelTransform(Transform(camMat)); - } + } } bool OverlayConductor::getEnabled() const { diff --git a/interface/src/ui/OverlayConductor.h b/interface/src/ui/OverlayConductor.h index 1ec66663a4..4ecbac5bcf 100644 --- a/interface/src/ui/OverlayConductor.h +++ b/interface/src/ui/OverlayConductor.h @@ -19,6 +19,7 @@ public: void update(float dt); void setEnabled(bool enable); bool getEnabled() const; + bool shouldCenterUI() const; private: void updateMode(); diff --git a/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp b/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp index d4fff1b976..2d3c79071f 100644 --- a/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp +++ b/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp @@ -336,7 +336,9 @@ void CompositorHelper::computeHmdPickRay(const glm::vec2& cursorPos, glm::vec3& } glm::mat4 CompositorHelper::getUiTransform() const { - return _currentCamera * glm::inverse(_currentDisplayPlugin->getHeadPose()); + glm::mat4 modelMat; + _modelTransform.getMatrix(modelMat); + return _currentCamera * glm::inverse(_currentDisplayPlugin->getHeadPose()) * modelMat; } //Finds the collision point of a world space ray diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp index 1616dcdb77..b9f6ad1a98 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp @@ -253,12 +253,13 @@ void HmdDisplayPlugin::compositeScene() { void HmdDisplayPlugin::compositeOverlay() { using namespace oglplus; auto compositorHelper = DependencyManager::get(); + glm::mat4 modelMat = compositorHelper->getModelTransform().getMatrix(); useProgram(_program); _sphereSection->Use(); for_each_eye([&](Eye eye) { eyeViewport(eye); - auto modelView = glm::inverse(_currentPresentFrameInfo.presentPose * getEyeToHeadTransform(eye)); + auto modelView = glm::inverse(_currentPresentFrameInfo.presentPose * getEyeToHeadTransform(eye)) * modelMat; auto mvp = _eyeProjections[eye] * modelView; Uniform(*_program, _mvpUniform).Set(mvp); _sphereSection->Draw(); From 6e3057479ffacde155330039d249b242210adb92 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 9 Jun 2016 11:27:02 -0700 Subject: [PATCH 2/7] fixes for warnings Also overlays menu option should work better in conjunction with ui-center/fading --- interface/src/ui/OverlayConductor.cpp | 4 +--- interface/src/ui/OverlayConductor.h | 1 - 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/interface/src/ui/OverlayConductor.cpp b/interface/src/ui/OverlayConductor.cpp index c4fd938e97..9e2e8cb0db 100644 --- a/interface/src/ui/OverlayConductor.cpp +++ b/interface/src/ui/OverlayConductor.cpp @@ -71,9 +71,7 @@ void OverlayConductor::update(float dt) { } else if ((usecTimestampNow() - _timeInPotentialMode) > (nowDriving ? REQUIRED_USECS_IN_NEW_MODE_BEFORE_INVISIBLE : REQUIRED_USECS_IN_NEW_MODE_BEFORE_VISIBLE)) { _timeInPotentialMode = 0; // a real transition bool wantsOverlays = Menu::getInstance()->isOptionChecked(MenuOption::Overlays); - if (wantsOverlays) { - setEnabled(!nowDriving); - } + setEnabled(!nowDriving && wantsOverlays); _driving = nowDriving; } diff --git a/interface/src/ui/OverlayConductor.h b/interface/src/ui/OverlayConductor.h index 4ecbac5bcf..2e425454c7 100644 --- a/interface/src/ui/OverlayConductor.h +++ b/interface/src/ui/OverlayConductor.h @@ -34,7 +34,6 @@ private: bool _enabled { false }; bool _driving { false }; quint64 _timeInPotentialMode { 0 }; - bool _wantsOverlays { true }; }; #endif From 685710d0ec088b60b13fe7aef0ba3054cb67155e Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 9 Jun 2016 18:09:04 -0700 Subject: [PATCH 3/7] OverlayConductor improvments * the HUD will fade/in when driving, even in desktop mode. * the HUD no longer pops when you lean outside of the UI sphere, instead it should smoothly fade out and fade back in. * the overlay toggle button should override fading while driving, as expected. * removed any notion of SITTING, STANDING or FLAT mode from overlay. --- interface/src/ui/OverlayConductor.cpp | 139 ++++++++++++-------------- interface/src/ui/OverlayConductor.h | 22 ++-- 2 files changed, 76 insertions(+), 85 deletions(-) diff --git a/interface/src/ui/OverlayConductor.cpp b/interface/src/ui/OverlayConductor.cpp index 9e2e8cb0db..790652aa0f 100644 --- a/interface/src/ui/OverlayConductor.cpp +++ b/interface/src/ui/OverlayConductor.cpp @@ -22,7 +22,7 @@ OverlayConductor::OverlayConductor() { OverlayConductor::~OverlayConductor() { } -bool OverlayConductor::shouldCenterUI() const { +bool OverlayConductor::headOutsideOverlay() const { glm::mat4 hmdMat = qApp->getHMDSensorPose(); glm::vec3 hmdPos = extractTranslation(hmdMat); @@ -41,89 +41,82 @@ bool OverlayConductor::shouldCenterUI() const { return false; } -void OverlayConductor::update(float dt) { - - updateMode(); - +bool OverlayConductor::avatarHasDriveInput() const { MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); - switch (_mode) { - case SITTING: { - // when sitting, the overlay is at the origin, facing down the -z axis. - // the camera is taken directly from the HMD. - Transform identity; - qApp->getApplicationCompositor().setModelTransform(identity); - qApp->getApplicationCompositor().setCameraBaseTransform(identity); - break; - } - case STANDING: { + const quint64 DRIVE_ENABLE_TIME_USECS = 200 * 1000; // 200 ms + const quint64 DRIVE_DISABLE_TIME_USECS = 1000 * 1000; // 1 s - const quint64 REQUIRED_USECS_IN_NEW_MODE_BEFORE_INVISIBLE = 200 * 1000; - const quint64 REQUIRED_USECS_IN_NEW_MODE_BEFORE_VISIBLE = 1000 * 1000; - - // fade in or out the overlay, based on driving. - bool nowDriving = myAvatar->hasDriveInput(); - // Check that we're in this new mode for long enough to really trigger a transition. - if (nowDriving == _driving) { // If there's no change in state, clear any attepted timer. - _timeInPotentialMode = 0; - } else if (_timeInPotentialMode == 0) { // We've just changed with no timer, so start timing now. - _timeInPotentialMode = usecTimestampNow(); - } else if ((usecTimestampNow() - _timeInPotentialMode) > (nowDriving ? REQUIRED_USECS_IN_NEW_MODE_BEFORE_INVISIBLE : REQUIRED_USECS_IN_NEW_MODE_BEFORE_VISIBLE)) { - _timeInPotentialMode = 0; // a real transition - bool wantsOverlays = Menu::getInstance()->isOptionChecked(MenuOption::Overlays); - setEnabled(!nowDriving && wantsOverlays); - _driving = nowDriving; - } - - // center the UI - if (shouldCenterUI()) { - Transform hmdTransform(cancelOutRollAndPitch(qApp->getHMDSensorPose())); - qApp->getApplicationCompositor().setModelTransform(hmdTransform); - } - break; + bool desiredDriving = myAvatar->hasDriveInput(); + if (desiredDriving != _desiredDriving) { + // start timer + _desiredDrivingTimer = usecTimestampNow() + (desiredDriving ? DRIVE_ENABLE_TIME_USECS : DRIVE_DISABLE_TIME_USECS); } - case FLAT: - // do nothing - break; + + _desiredDriving = desiredDriving; + + if (_desiredDrivingTimer != 0 && usecTimestampNow() > _desiredDrivingTimer) { + // timer expired + // change state! + _currentDriving = _desiredDriving; + // disable timer + _desiredDrivingTimer = 0; } + + return _currentDriving; } -void OverlayConductor::updateMode() { +bool OverlayConductor::shouldShowOverlay() const { MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); - Mode newMode; - if (qApp->isHMDMode()) { - if (myAvatar->getClearOverlayWhenDriving()) { - newMode = STANDING; - } else { - newMode = SITTING; - } - } else { - newMode = FLAT; +#ifdef WANT_DEBUG + qDebug() << "AJT: wantsOverlays =" << Menu::getInstance()->isOptionChecked(MenuOption::Overlays) << ", clearOverlayWhenDriving =" << myAvatar->getClearOverlayWhenDriving() << + ", headOutsideOverlay =" << headOutsideOverlay() << ", hasDriveInput =" << avatarHasDriveInput(); +#endif + + return Menu::getInstance()->isOptionChecked(MenuOption::Overlays) && (!myAvatar->getClearOverlayWhenDriving() || (!headOutsideOverlay() && !avatarHasDriveInput())); +} + +bool OverlayConductor::shouldRecenterOnFadeOut() const { + MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); + return Menu::getInstance()->isOptionChecked(MenuOption::Overlays) && myAvatar->getClearOverlayWhenDriving() && headOutsideOverlay(); +} + +void OverlayConductor::centerUI() { + // place the overlay at the current hmd position in sensor space + auto camMat = cancelOutRollAndPitch(qApp->getHMDSensorPose()); + qApp->getApplicationCompositor().setModelTransform(Transform(camMat)); +} + +void OverlayConductor::update(float dt) { + + // centerUI if hmd mode changes + if (qApp->isHMDMode() && !_hmdMode) { + centerUI(); + } + _hmdMode = qApp->isHMDMode(); + + // centerUI if timer expires + if (_fadeOutTime != 0 && usecTimestampNow() > _fadeOutTime) { + // fade out timer expired + _fadeOutTime = 0; + centerUI(); } - if (newMode != _mode) { - switch (newMode) { - case SITTING: { - // enter the SITTING state - // place the overlay at origin - qApp->getApplicationCompositor().setModelTransform(Transform()); - break; - } - case STANDING: { - // enter the STANDING state - Transform hmdTransform(cancelOutRollAndPitch(qApp->getHMDSensorPose())); - qApp->getApplicationCompositor().setModelTransform(hmdTransform); - break; - } + bool showOverlay = shouldShowOverlay(); - case FLAT: - // do nothing - break; + if (showOverlay != getEnabled()) { + if (showOverlay) { + // disable fadeOut timer + _fadeOutTime = 0; + } else if (shouldRecenterOnFadeOut()) { + // start fadeOut timer + const quint64 FADE_OUT_TIME_USECS = 300 * 1000; // 300 ms + _fadeOutTime = usecTimestampNow() + FADE_OUT_TIME_USECS; } } - _mode = newMode; + setEnabled(showOverlay); } void OverlayConductor::setEnabled(bool enabled) { @@ -135,11 +128,9 @@ void OverlayConductor::setEnabled(bool enabled) { auto offscreenUi = DependencyManager::get(); offscreenUi->setPinned(!_enabled); // if the new state is visible/enabled... - if (_enabled && _mode == STANDING) { - // place the overlay at the current hmd position in world space - MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); - auto camMat = cancelOutRollAndPitch(myAvatar->getSensorToWorldMatrix() * qApp->getHMDSensorPose()); - qApp->getApplicationCompositor().setModelTransform(Transform(camMat)); + MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); + if (_enabled && myAvatar->getClearOverlayWhenDriving() && qApp->isHMDMode()) { + centerUI(); } } diff --git a/interface/src/ui/OverlayConductor.h b/interface/src/ui/OverlayConductor.h index 2e425454c7..40ccb4b91f 100644 --- a/interface/src/ui/OverlayConductor.h +++ b/interface/src/ui/OverlayConductor.h @@ -19,21 +19,21 @@ public: void update(float dt); void setEnabled(bool enable); bool getEnabled() const; - bool shouldCenterUI() const; private: - void updateMode(); + bool headOutsideOverlay() const; + bool avatarHasDriveInput() const; + bool shouldShowOverlay() const; + bool shouldRecenterOnFadeOut() const; + void centerUI(); - enum Mode { - FLAT, - SITTING, - STANDING - }; - - Mode _mode { FLAT }; + quint64 _fadeOutTime { 0 }; bool _enabled { false }; - bool _driving { false }; - quint64 _timeInPotentialMode { 0 }; + bool _hmdMode { false }; + + mutable quint64 _desiredDrivingTimer { 0 }; + mutable bool _desiredDriving { false }; + mutable bool _currentDriving { false }; }; #endif From f6ed5a1dae57820a320fa53162960b600bebce47 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Fri, 10 Jun 2016 18:33:40 -0700 Subject: [PATCH 4/7] Bugfixes based on feedback * When the overlay is hidden because your head is too close to the sphere, instead of coming back immediately, it waits until the avatar's velocity is near zero for a period of time. * Hooked up jump and fly to MyAvatar::hasDriveInput() * Added an internal state machine to OverlayConductor to manage hiding/showing transitions. * The overlay menu state is now tied directly to the overlay, so it will change state as the overlay is dynamically hidden/shown from code. * Removed slot going directly from MenuOption::Overlays directly to OverlayConductor::setEnable(). --- interface/src/Menu.cpp | 3 +- interface/src/avatar/MyAvatar.cpp | 2 +- interface/src/ui/OverlayConductor.cpp | 139 +++++++++++++++++++------- interface/src/ui/OverlayConductor.h | 35 +++++-- 4 files changed, 131 insertions(+), 48 deletions(-) diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 031564fa7a..0c1f2116d9 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -256,8 +256,7 @@ Menu::Menu() { UNSPECIFIED_POSITION, "Advanced"); // View > Overlays - addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Overlays, 0, true, - qApp, SLOT(setOverlaysVisible(bool))); + addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Overlays, 0, true); // Navigate menu ---------------------------------- MenuWrapper* navigateMenu = addMenu("Navigate"); diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 76e48d34d6..12fe7c4ac2 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -2136,5 +2136,5 @@ bool MyAvatar::didTeleport() { } bool MyAvatar::hasDriveInput() const { - return fabsf(_driveKeys[TRANSLATE_X]) > 0.0f || fabsf(_driveKeys[TRANSLATE_Z]) > 0.0f; + return fabsf(_driveKeys[TRANSLATE_X]) > 0.0f || fabsf(_driveKeys[TRANSLATE_Y]) > 0.0f || fabsf(_driveKeys[TRANSLATE_Z]) > 0.0f; } diff --git a/interface/src/ui/OverlayConductor.cpp b/interface/src/ui/OverlayConductor.cpp index 790652aa0f..01649a0b3a 100644 --- a/interface/src/ui/OverlayConductor.cpp +++ b/interface/src/ui/OverlayConductor.cpp @@ -17,13 +17,13 @@ #include "OverlayConductor.h" OverlayConductor::OverlayConductor() { + } OverlayConductor::~OverlayConductor() { } bool OverlayConductor::headOutsideOverlay() const { - glm::mat4 hmdMat = qApp->getHMDSensorPose(); glm::vec3 hmdPos = extractTranslation(hmdMat); glm::vec3 hmdForward = transformVectorFast(hmdMat, glm::vec3(0.0f, 0.0f, -1.0f)); @@ -41,7 +41,34 @@ bool OverlayConductor::headOutsideOverlay() const { return false; } -bool OverlayConductor::avatarHasDriveInput() const { +bool OverlayConductor::updateAvatarIsAtRest() { + + MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); + + const quint64 REST_ENABLE_TIME_USECS = 1000 * 1000; // 1 s + const quint64 REST_DISABLE_TIME_USECS = 200 * 1000; // 200 ms + + const float AT_REST_THRESHOLD = 0.01f; + bool desiredAtRest = glm::length(myAvatar->getVelocity()) < AT_REST_THRESHOLD; + if (desiredAtRest != _desiredAtRest) { + // start timer + _desiredAtRestTimer = usecTimestampNow() + (desiredAtRest ? REST_ENABLE_TIME_USECS : REST_DISABLE_TIME_USECS); + } + + _desiredAtRest = desiredAtRest; + + if (_desiredAtRestTimer != 0 && usecTimestampNow() > _desiredAtRestTimer) { + // timer expired + // change state! + _currentAtRest = _desiredAtRest; + // disable timer + _desiredAtRestTimer = 0; + } + + return _currentAtRest; +} + +bool OverlayConductor::updateAvatarHasDriveInput() { MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); const quint64 DRIVE_ENABLE_TIME_USECS = 200 * 1000; // 200 ms @@ -66,57 +93,88 @@ bool OverlayConductor::avatarHasDriveInput() const { return _currentDriving; } -bool OverlayConductor::shouldShowOverlay() const { - MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); - -#ifdef WANT_DEBUG - qDebug() << "AJT: wantsOverlays =" << Menu::getInstance()->isOptionChecked(MenuOption::Overlays) << ", clearOverlayWhenDriving =" << myAvatar->getClearOverlayWhenDriving() << - ", headOutsideOverlay =" << headOutsideOverlay() << ", hasDriveInput =" << avatarHasDriveInput(); -#endif - - return Menu::getInstance()->isOptionChecked(MenuOption::Overlays) && (!myAvatar->getClearOverlayWhenDriving() || (!headOutsideOverlay() && !avatarHasDriveInput())); -} - -bool OverlayConductor::shouldRecenterOnFadeOut() const { - MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); - return Menu::getInstance()->isOptionChecked(MenuOption::Overlays) && myAvatar->getClearOverlayWhenDriving() && headOutsideOverlay(); -} - void OverlayConductor::centerUI() { // place the overlay at the current hmd position in sensor space auto camMat = cancelOutRollAndPitch(qApp->getHMDSensorPose()); qApp->getApplicationCompositor().setModelTransform(Transform(camMat)); } +bool OverlayConductor::userWishesToHide() const { + // user pressed toggle button. + return Menu::getInstance()->isOptionChecked(MenuOption::Overlays) != _prevOverlayMenuChecked && Menu::getInstance()->isOptionChecked(MenuOption::Overlays); +} + +bool OverlayConductor::userWishesToShow() const { + // user pressed toggle button. + return Menu::getInstance()->isOptionChecked(MenuOption::Overlays) != _prevOverlayMenuChecked && !Menu::getInstance()->isOptionChecked(MenuOption::Overlays); +} + +void OverlayConductor::setState(State state) { +#ifdef WANT_DEBUG + static QString stateToString[NumStates] = { "Enabled", "DisabledByDrive", "DisabledByHead", "DisabledByToggle" }; + qDebug() << "OverlayConductor " << stateToString[state] << "<--" << stateToString[_state]; +#endif + _state = state; +} + +OverlayConductor::State OverlayConductor::getState() const { + return _state; +} + void OverlayConductor::update(float dt) { - // centerUI if hmd mode changes + MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); + + // centerUI when hmd mode is first enabled if (qApp->isHMDMode() && !_hmdMode) { centerUI(); } _hmdMode = qApp->isHMDMode(); - // centerUI if timer expires - if (_fadeOutTime != 0 && usecTimestampNow() > _fadeOutTime) { - // fade out timer expired - _fadeOutTime = 0; - centerUI(); + bool prevDriving = _currentDriving; + bool isDriving = updateAvatarHasDriveInput(); + bool drivingChanged = prevDriving != isDriving; + + bool isAtRest = updateAvatarIsAtRest(); + + switch (getState()) { + case Enabled: + if (qApp->isHMDMode() && headOutsideOverlay()) { + setState(DisabledByHead); + setEnabled(false); + } + if (userWishesToHide()) { + setState(DisabledByToggle); + setEnabled(false); + } + if (drivingChanged && isDriving) { + setState(DisabledByDrive); + setEnabled(false); + } + break; + case DisabledByDrive: + if (!isDriving || userWishesToShow()) { + setState(Enabled); + setEnabled(true); + } + break; + case DisabledByHead: + if (isAtRest || userWishesToShow()) { + setState(Enabled); + setEnabled(true); + } + break; + case DisabledByToggle: + if (userWishesToShow()) { + setState(Enabled); + setEnabled(true); + } + break; + default: + break; } - bool showOverlay = shouldShowOverlay(); - - if (showOverlay != getEnabled()) { - if (showOverlay) { - // disable fadeOut timer - _fadeOutTime = 0; - } else if (shouldRecenterOnFadeOut()) { - // start fadeOut timer - const quint64 FADE_OUT_TIME_USECS = 300 * 1000; // 300 ms - _fadeOutTime = usecTimestampNow() + FADE_OUT_TIME_USECS; - } - } - - setEnabled(showOverlay); + _prevOverlayMenuChecked = Menu::getInstance()->isOptionChecked(MenuOption::Overlays); } void OverlayConductor::setEnabled(bool enabled) { @@ -127,6 +185,11 @@ void OverlayConductor::setEnabled(bool enabled) { _enabled = enabled; // set the new value auto offscreenUi = DependencyManager::get(); offscreenUi->setPinned(!_enabled); + + // ensure that the the state of the menu item reflects the state of the overlay. + Menu::getInstance()->setIsOptionChecked(MenuOption::Overlays, _enabled); + _prevOverlayMenuChecked = _enabled; + // if the new state is visible/enabled... MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); if (_enabled && myAvatar->getClearOverlayWhenDriving() && qApp->isHMDMode()) { diff --git a/interface/src/ui/OverlayConductor.h b/interface/src/ui/OverlayConductor.h index 40ccb4b91f..83d6957012 100644 --- a/interface/src/ui/OverlayConductor.h +++ b/interface/src/ui/OverlayConductor.h @@ -22,18 +22,39 @@ public: private: bool headOutsideOverlay() const; - bool avatarHasDriveInput() const; - bool shouldShowOverlay() const; - bool shouldRecenterOnFadeOut() const; + bool updateAvatarHasDriveInput(); + bool updateAvatarIsAtRest(); + bool userWishesToHide() const; + bool userWishesToShow() const; void centerUI(); - quint64 _fadeOutTime { 0 }; + enum State { + Enabled = 0, + DisabledByDrive, + DisabledByHead, + DisabledByToggle, + NumStates + }; + + void setState(State state); + State getState() const; + + State _state { DisabledByDrive }; + + bool _prevOverlayMenuChecked { true }; bool _enabled { false }; bool _hmdMode { false }; + bool _disabledFromHead { false }; - mutable quint64 _desiredDrivingTimer { 0 }; - mutable bool _desiredDriving { false }; - mutable bool _currentDriving { false }; + // used by updateAvatarHasDriveInput + quint64 _desiredDrivingTimer { 0 }; + bool _desiredDriving { false }; + bool _currentDriving { false }; + + // used by updateAvatarIsAtRest + quint64 _desiredAtRestTimer { 0 }; + bool _desiredAtRest { true }; + bool _currentAtRest { true }; }; #endif From ab4bef7d55cbef9063ad0a53e85d5353e5fb66eb Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 13 Jun 2016 11:19:47 -0700 Subject: [PATCH 5/7] Fixes based on PR feedback * The "Clear Overlay When Driving" avatar preference is obeyed. * sensor reset will also center the ui. --- interface/src/Application.cpp | 1 + interface/src/ui/OverlayConductor.cpp | 6 +++--- interface/src/ui/OverlayConductor.h | 3 ++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 1e6f7ba995..e494142302 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -4209,6 +4209,7 @@ void Application::resetSensors(bool andReload) { DependencyManager::get()->reset(); DependencyManager::get()->reset(); getActiveDisplayPlugin()->resetSensors(); + _overlayConductor.centerUI(); getMyAvatar()->reset(andReload); QMetaObject::invokeMethod(DependencyManager::get().data(), "reset", Qt::QueuedConnection); } diff --git a/interface/src/ui/OverlayConductor.cpp b/interface/src/ui/OverlayConductor.cpp index 01649a0b3a..b653d0f445 100644 --- a/interface/src/ui/OverlayConductor.cpp +++ b/interface/src/ui/OverlayConductor.cpp @@ -139,7 +139,7 @@ void OverlayConductor::update(float dt) { switch (getState()) { case Enabled: - if (qApp->isHMDMode() && headOutsideOverlay()) { + if (myAvatar->getClearOverlayWhenDriving() && qApp->isHMDMode() && headOutsideOverlay()) { setState(DisabledByHead); setEnabled(false); } @@ -147,7 +147,7 @@ void OverlayConductor::update(float dt) { setState(DisabledByToggle); setEnabled(false); } - if (drivingChanged && isDriving) { + if (myAvatar->getClearOverlayWhenDriving() && drivingChanged && isDriving) { setState(DisabledByDrive); setEnabled(false); } @@ -192,7 +192,7 @@ void OverlayConductor::setEnabled(bool enabled) { // if the new state is visible/enabled... MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); - if (_enabled && myAvatar->getClearOverlayWhenDriving() && qApp->isHMDMode()) { + if (_enabled && qApp->isHMDMode()) { centerUI(); } } diff --git a/interface/src/ui/OverlayConductor.h b/interface/src/ui/OverlayConductor.h index 83d6957012..fcfdac72a5 100644 --- a/interface/src/ui/OverlayConductor.h +++ b/interface/src/ui/OverlayConductor.h @@ -20,13 +20,14 @@ public: void setEnabled(bool enable); bool getEnabled() const; + void centerUI(); + private: bool headOutsideOverlay() const; bool updateAvatarHasDriveInput(); bool updateAvatarIsAtRest(); bool userWishesToHide() const; bool userWishesToShow() const; - void centerUI(); enum State { Enabled = 0, From 1ca1f98034731127e00954405b8c8df5e8a36aff Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 13 Jun 2016 12:01:42 -0700 Subject: [PATCH 6/7] Fix unused variable warning --- interface/src/ui/OverlayConductor.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/interface/src/ui/OverlayConductor.cpp b/interface/src/ui/OverlayConductor.cpp index b653d0f445..a897d85472 100644 --- a/interface/src/ui/OverlayConductor.cpp +++ b/interface/src/ui/OverlayConductor.cpp @@ -191,7 +191,6 @@ void OverlayConductor::setEnabled(bool enabled) { _prevOverlayMenuChecked = _enabled; // if the new state is visible/enabled... - MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); if (_enabled && qApp->isHMDMode()) { centerUI(); } From 21e67fc4d844c8811231029fbb6fe60e24ac5dfc Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 13 Jun 2016 12:03:42 -0700 Subject: [PATCH 7/7] Another unused variable warning --- interface/src/ui/OverlayConductor.h | 1 - 1 file changed, 1 deletion(-) diff --git a/interface/src/ui/OverlayConductor.h b/interface/src/ui/OverlayConductor.h index fcfdac72a5..375b2652f6 100644 --- a/interface/src/ui/OverlayConductor.h +++ b/interface/src/ui/OverlayConductor.h @@ -45,7 +45,6 @@ private: bool _prevOverlayMenuChecked { true }; bool _enabled { false }; bool _hmdMode { false }; - bool _disabledFromHead { false }; // used by updateAvatarHasDriveInput quint64 _desiredDrivingTimer { 0 };