diff --git a/src/android/app/src/main/jni/vr/layers/GameSurfaceLayer.cpp b/src/android/app/src/main/jni/vr/layers/GameSurfaceLayer.cpp index 80d4bb640..bbf8c755b 100644 --- a/src/android/app/src/main/jni/vr/layers/GameSurfaceLayer.cpp +++ b/src/android/app/src/main/jni/vr/layers/GameSurfaceLayer.cpp @@ -436,6 +436,30 @@ void GameSurfaceLayer::SetTopPanelFromController(const XrVector3f& controllerPos mTopPanel.mPanelFromWorld = XrPosef{windowRotation, windowPosition}; } +void GameSurfaceLayer::SetTopPanelFromThumbstick(const float thumbstickY) { + static constexpr float kDepthSpeed = 0.05f; + static constexpr float kMaxDepth = -10.0f; + + mTopPanel.mPanelFromWorld.position.z -= (thumbstickY * kDepthSpeed); + mTopPanel.mPanelFromWorld.position.z = + std::min(mTopPanel.mPanelFromWorld.position.z, mLowerPanel.mPanelFromWorld.position.z); + mTopPanel.mPanelFromWorld.position.z = + std::max(mTopPanel.mPanelFromWorld.position.z, kMaxDepth); +} + +XrPosef GameSurfaceLayer::SetLowerPanelFromThumbstick(const float thumbstickY) { + static constexpr float kDepthSpeed = 0.05f; + static constexpr float kMaxDepth = -10.0f; + + mLowerPanel.mPanelFromWorld.position.y += (thumbstickY * kDepthSpeed); + mLowerPanel.mPanelFromWorld.position.y = + std::min(mLowerPanel.mPanelFromWorld.position.y, mTopPanel.mPanelFromWorld.position.y); + mLowerPanel.mPanelFromWorld.position.y = + std::max(mLowerPanel.mPanelFromWorld.position.y, kMaxDepth); + + return mLowerPanel.mPanelFromWorld; +} + XrPosef GameSurfaceLayer::GetTopPanelFromHeadPose(uint32_t eye, const XrPosef& headPose) { XrVector3f panelPosition = headPose.position; @@ -460,17 +484,6 @@ XrPosef GameSurfaceLayer::GetTopPanelFromHeadPose(uint32_t eye, const XrPosef& h } // based on thumbstick, modify the depth of the top panel -void GameSurfaceLayer::SetTopPanelFromThumbstick(const float thumbstickY) { - static constexpr float kDepthSpeed = 0.05f; - static constexpr float kMaxDepth = -10.0f; - - mTopPanel.mPanelFromWorld.position.z -= (thumbstickY * kDepthSpeed); - mTopPanel.mPanelFromWorld.position.z = - std::min(mTopPanel.mPanelFromWorld.position.z, mLowerPanel.mPanelFromWorld.position.z); - mTopPanel.mPanelFromWorld.position.z = - std::max(mTopPanel.mPanelFromWorld.position.z, kMaxDepth); -} - // Next error code: -2 int32_t GameSurfaceLayer::Init(const XrSession& session, const jobject activityObject) { if (mImmersiveMode > 0) { diff --git a/src/android/app/src/main/jni/vr/layers/GameSurfaceLayer.h b/src/android/app/src/main/jni/vr/layers/GameSurfaceLayer.h index d3d74c382..cab6a2a5f 100644 --- a/src/android/app/src/main/jni/vr/layers/GameSurfaceLayer.h +++ b/src/android/app/src/main/jni/vr/layers/GameSurfaceLayer.h @@ -158,18 +158,19 @@ public: * * Note: assumes viewer is looking down the -Z axis. */ - bool GetRayIntersectionWithPanel(const XrVector3f& start, - const XrVector3f& end, - XrVector2f& result2d, - XrPosef& result3d) const; - bool GetRayIntersectionWithPanelTopPanel(const XrVector3f& start, - const XrVector3f& end, - XrVector2f& result2d, - XrPosef& result3d) const; - void SetTopPanelFromController(const XrVector3f& controllerPosition); - XrPosef GetTopPanelFromHeadPose(uint32_t eye, const XrPosef& headPose); - + bool GetRayIntersectionWithPanel(const XrVector3f& start, + const XrVector3f& end, + XrVector2f& result2d, + XrPosef& result3d) const; + bool GetRayIntersectionWithPanelTopPanel(const XrVector3f& start, + const XrVector3f& end, + XrVector2f& result2d, + XrPosef& result3d) const; + void SetTopPanelFromController(const XrVector3f& controllerPosition); void SetTopPanelFromThumbstick(const float thumbstickY); + XrPosef SetLowerPanelFromThumbstick(const float thumbstickY); + + XrPosef GetTopPanelFromHeadPose(uint32_t eye, const XrPosef& headPose); private: int Init(const XrSession& session, const jobject activityObject); diff --git a/src/android/app/src/main/jni/vr/layers/UILayer.cpp b/src/android/app/src/main/jni/vr/layers/UILayer.cpp index be1fae443..b12afc071 100644 --- a/src/android/app/src/main/jni/vr/layers/UILayer.cpp +++ b/src/android/app/src/main/jni/vr/layers/UILayer.cpp @@ -325,3 +325,5 @@ int UILayer::CreateSwapchain() { void UILayer::SendClickToUI(const XrVector2f& pos2d, const int type) { mEnv->CallIntMethod(mVrUILayerObject, mSendClickToUIMethodID, pos2d.x, pos2d.y, type); } + +void UILayer::SetPanelWithPose(const XrPosef& pose) { mPanelFromWorld = pose; } diff --git a/src/android/app/src/main/jni/vr/layers/UILayer.h b/src/android/app/src/main/jni/vr/layers/UILayer.h index 0950f2407..a0397fb32 100644 --- a/src/android/app/src/main/jni/vr/layers/UILayer.h +++ b/src/android/app/src/main/jni/vr/layers/UILayer.h @@ -123,6 +123,8 @@ public: */ void SendClickToUI(const XrVector2f& pos2d, const int type); + void SetPanelWithPose(const XrPosef& pose); + private: int Init(const std::string& className, const jobject activityObject, const XrVector3f& position, const XrSession& session); diff --git a/src/android/app/src/main/jni/vr/vr_main.cpp b/src/android/app/src/main/jni/vr/vr_main.cpp index d3cd074bd..6890c9730 100644 --- a/src/android/app/src/main/jni/vr/vr_main.cpp +++ b/src/android/app/src/main/jni/vr/vr_main.cpp @@ -650,7 +650,7 @@ private: XrPosef cursorPose3d = XrMath::Posef::Identity(); XrVector2f cursorPos2d = {0, 0}; float scaleFactor = 0.01f; - CursorLayer::CursorType cursorType = CursorLayer::CursorType::CURSOR_TYPE_NORMAL; + CursorLayer::CursorType cursorType = appState.mLowerMenuType == LowerMenuType::POSITIONAL_MENU ? CursorLayer::CursorType::CURSOR_TYPE_TOP_PANEL : CursorLayer::CursorType::CURSOR_TYPE_NORMAL; [[maybe_unused]] const auto nonPreferredController = mInputStateFrame.mPreferredHand == InputStateFrame::LEFT_CONTROLLER @@ -685,86 +685,100 @@ private: if (triggerState.changedSinceLastSync) { mErrorMessageLayer->SendClickToUI(cursorPos2d, triggerState.currentState); } - } else { // Don't test for cursor intersection if error message is shown - if (appState.mIsKeyboardActive) { - shouldRenderCursor = mKeyboardLayer->GetRayIntersectionWithPanel( - start, end, cursorPos2d, cursorPose3d); - if (triggerState.changedSinceLastSync) { - mKeyboardLayer->SendClickToUI(cursorPos2d, triggerState.currentState); - } - } else { - // No dialogs/popups that should impede normal cursor interaction with - // applicable panels - shouldRenderCursor = mGameSurfaceLayer->GetRayIntersectionWithPanel( - start, end, cursorPos2d, cursorPose3d); - if (triggerState.currentState == 0 && triggerState.changedSinceLastSync) { - jni->CallVoidMethod(mActivityObject, mSendClickToWindowMethodID, - cursorPos2d.x, cursorPos2d.y, 0); - } else if (triggerState.changedSinceLastSync && - triggerState.currentState == 1) { - jni->CallVoidMethod(mActivityObject, mSendClickToWindowMethodID, - cursorPos2d.x, cursorPos2d.y, 1); - } else if (triggerState.currentState == 1 && - !triggerState.changedSinceLastSync) { - - jni->CallVoidMethod(mActivityObject, mSendClickToWindowMethodID, - cursorPos2d.x, cursorPos2d.y, 2); - } - if (!shouldRenderCursor) { - shouldRenderCursor = mRibbonLayer->GetRayIntersectionWithPanel( - start, end, cursorPos2d, cursorPose3d); - if (triggerState.changedSinceLastSync) { - mRibbonLayer->SendClickToUI(cursorPos2d, triggerState.currentState); - } - } - - if (!shouldRenderCursor) { - // Handling this here means L2/R2 are liable to - // be slightly out of sync with the other - // buttons (which are handled before - // WaitFrame()). We'll see if that ends up being - // a problem for any games. - ForwardButtonStateIfNeeded( - jni, mActivityObject, mForwardVRInputMethodID, 104 /* BUTTON_L2 */, - mInputStateFrame - .mIndexTriggerState[InputStateFrame::LEFT_CONTROLLER], - "l2"); - ForwardButtonStateIfNeeded( - jni, mActivityObject, mForwardVRInputMethodID, 105 /* BUTTON_R2 */, - mInputStateFrame - .mIndexTriggerState[InputStateFrame::RIGHT_CONTROLLER], - "r2"); - } + } + // Don't test for cursor intersection if error message is shown + if (!shouldRenderCursor && appState.mIsKeyboardActive) { + shouldRenderCursor = mKeyboardLayer->GetRayIntersectionWithPanel( + start, end, cursorPos2d, cursorPose3d); + if (triggerState.changedSinceLastSync) { + mKeyboardLayer->SendClickToUI(cursorPos2d, triggerState.currentState); } + } + // No dialogs/popups that should impede normal cursor interaction with + // applicable panels + if (!shouldRenderCursor && appState.ShouldShowLowerPanel()) { + shouldRenderCursor = mGameSurfaceLayer->GetRayIntersectionWithPanel( + start, end, cursorPos2d, cursorPose3d); + if (triggerState.currentState == 0 && triggerState.changedSinceLastSync) { + jni->CallVoidMethod(mActivityObject, mSendClickToWindowMethodID, + cursorPos2d.x, cursorPos2d.y, 0); + } else if (triggerState.changedSinceLastSync && + triggerState.currentState == 1) { + jni->CallVoidMethod(mActivityObject, mSendClickToWindowMethodID, + cursorPos2d.x, cursorPos2d.y, 1); + } else if (triggerState.currentState == 1 && + !triggerState.changedSinceLastSync) { - // Hit test the top panel - if (!shouldRenderCursor) { - shouldRenderCursor = mGameSurfaceLayer->GetRayIntersectionWithPanelTopPanel( - start, end, cursorPos2d, cursorPose3d); - // If top panel is hit, trigger controls the - // position/rotation - if (shouldRenderCursor && triggerState.currentState) { - // null out X component -- screen should stay - // center - mGameSurfaceLayer->SetTopPanelFromController( - XrVector3f{0, cursorPose3d.position.y, cursorPose3d.position.z}); - // If trigger is pressed, thumbstick controls - // the depth - const XrActionStateVector2f& thumbstickState = - mInputStateFrame.mThumbStickState[mInputStateFrame.mPreferredHand]; + jni->CallVoidMethod(mActivityObject, mSendClickToWindowMethodID, + cursorPos2d.x, cursorPos2d.y, 2); + } + } + if (!shouldRenderCursor) { + shouldRenderCursor = mRibbonLayer->GetRayIntersectionWithPanel( + start, end, cursorPos2d, cursorPose3d); + if (shouldRenderCursor && triggerState.changedSinceLastSync) { + mRibbonLayer->SendClickToUI(cursorPos2d, triggerState.currentState); + } + if (shouldRenderCursor && + appState.mLowerMenuType == LowerMenuType::POSITIONAL_MENU && + triggerState.currentState) { - static constexpr float kThumbStickDirectionThreshold = 0.5f; - if (std::abs(thumbstickState.currentState.y) > - kThumbStickDirectionThreshold) { - mGameSurfaceLayer->SetTopPanelFromThumbstick( - thumbstickState.currentState.y); - } - } - if (shouldRenderCursor) { - cursorType = CursorLayer::CursorType::CURSOR_TYPE_TOP_PANEL; + static constexpr float kThumbStickDirectionThreshold = 0.5f; + // If trigger is pressed, thumbstick controls + // the depth + const XrActionStateVector2f& thumbstickState = + mInputStateFrame.mThumbStickState[mInputStateFrame.mPreferredHand]; + if (std::abs(thumbstickState.currentState.y) > + kThumbStickDirectionThreshold) { + const XrPosef lowerPanelPose = mGameSurfaceLayer->SetLowerPanelFromThumbstick( + thumbstickState.currentState.y); + mRibbonLayer->SetPanelWithPose(lowerPanelPose); } } } + + if (!shouldRenderCursor) { + // Handling this here means L2/R2 are liable to + // be slightly out of sync with the other + // buttons (which are handled before + // WaitFrame()). We'll see if that ends up being + // a problem for any games. + ForwardButtonStateIfNeeded( + jni, mActivityObject, mForwardVRInputMethodID, 104 /* BUTTON_L2 */, + mInputStateFrame.mIndexTriggerState[InputStateFrame::LEFT_CONTROLLER], + "l2"); + ForwardButtonStateIfNeeded( + jni, mActivityObject, mForwardVRInputMethodID, 105 /* BUTTON_R2 */, + mInputStateFrame.mIndexTriggerState[InputStateFrame::RIGHT_CONTROLLER], + "r2"); + } + + // Hit test the top panel if positional menu is active. + if (!shouldRenderCursor && + appState.mLowerMenuType == LowerMenuType::POSITIONAL_MENU) { + shouldRenderCursor = mGameSurfaceLayer->GetRayIntersectionWithPanelTopPanel( + start, end, cursorPos2d, cursorPose3d); + // If top panel is hit, trigger controls the + // position/rotation + if (shouldRenderCursor && triggerState.currentState) { + // null out X component -- screen should stay + // center + mGameSurfaceLayer->SetTopPanelFromController( + XrVector3f{0, cursorPose3d.position.y, cursorPose3d.position.z}); + // If trigger is pressed, thumbstick controls + // the depth + const XrActionStateVector2f& thumbstickState = + mInputStateFrame.mThumbStickState[mInputStateFrame.mPreferredHand]; + + static constexpr float kThumbStickDirectionThreshold = 0.5f; + if (std::abs(thumbstickState.currentState.y) > + kThumbStickDirectionThreshold) { + mGameSurfaceLayer->SetTopPanelFromThumbstick( + thumbstickState.currentState.y); + } + } + } + // Add a scale factor so the cursor doesn't scale as // quickly as the panel(s) with distance. This may be // mildly unsettling, but it helps to ensure the cursor @@ -1065,9 +1079,7 @@ private: class AppState { public: - bool ShouldShowLowerPanel() const { - return mLowerMenuType == LowerMenuType::MAIN_MENU; - } + bool ShouldShowLowerPanel() const { return mLowerMenuType == LowerMenuType::MAIN_MENU; } LowerMenuType mLowerMenuType = LowerMenuType::MAIN_MENU; diff --git a/src/android/app/src/main/res/layout/vr_ribbon_position_panel.xml b/src/android/app/src/main/res/layout/vr_ribbon_position_panel.xml index 3ea65b389..8ffd6c371 100644 --- a/src/android/app/src/main/res/layout/vr_ribbon_position_panel.xml +++ b/src/android/app/src/main/res/layout/vr_ribbon_position_panel.xml @@ -2,15 +2,39 @@ + android:gravity="center" + android:layout_width="match_parent" + android:layout_height="match_parent"> - + app:layout_constraintTop_toTopOf="parent"> + + + + + + + \ No newline at end of file