mirror of
https://github.com/amwatson/CitraVR.git
synced 2024-09-20 03:11:40 +02:00
position lower panel based on controller position, with a rotational bias
This commit is contained in:
parent
01b35c0def
commit
a5dcfc0e37
3 changed files with 82 additions and 51 deletions
|
@ -30,8 +30,10 @@ License : Licensed under GPLv3 or any later version.
|
|||
|
||||
namespace {
|
||||
|
||||
constexpr float kSuperImmersiveRadius = 0.5f;
|
||||
constexpr float kDistanceBetweenPanelsInMeters = 0.75f;
|
||||
constexpr float kSuperImmersiveRadius = 0.5f;
|
||||
constexpr float kDistanceBetweenPanelsInMeters = 0.75f;
|
||||
constexpr float kInitialLowerPanelPitchInRadians = -MATH_FLOAT_PI / 4.0f; // -45 degrees in radians
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Local sysprops
|
||||
|
||||
|
@ -124,17 +126,16 @@ Panel CreateTopPanel(const XrVector3f& position, const float surfaceWidth,
|
|||
Panel CreateLowerPanelFromTopPanel(const Panel& topPanel, const float resolutionFactor) {
|
||||
// Note: the fact that two constants are 0.75 is purely coincidental.
|
||||
constexpr float kDefaultLowerPanelScaleFactor = 0.75f * 0.75f;
|
||||
constexpr float kLowerPanelYOffsetInMeters = -kDistanceBetweenPanelsInMeters;
|
||||
constexpr float kLowerPanelZOffsetInMeters = -1.5f;
|
||||
constexpr float kLowerPanelYOffsetInMeters = -0.75f;
|
||||
constexpr float kLowerPanelZOffsetInMeters = 0.5f;
|
||||
// Pitch the lower panel away from the viewer 45 degrees
|
||||
constexpr float kLowerPanelPitchInRadians = -MATH_FLOAT_PI / 4.0f;
|
||||
const float cropHoriz = 90.0f * resolutionFactor;
|
||||
const float cropHoriz = 90.0f * resolutionFactor;
|
||||
|
||||
XrPosef lowerPanelFromWorld = topPanel.mPanelFromWorld;
|
||||
lowerPanelFromWorld.orientation =
|
||||
XrMath::Quatf::FromEuler(0.0f, kLowerPanelPitchInRadians, 0.0f);
|
||||
XrMath::Quatf::FromEuler(kInitialLowerPanelPitchInRadians, 0, 0);
|
||||
lowerPanelFromWorld.position.y += kLowerPanelYOffsetInMeters;
|
||||
lowerPanelFromWorld.position.z = kLowerPanelZOffsetInMeters;
|
||||
lowerPanelFromWorld.position.z += kLowerPanelZOffsetInMeters;
|
||||
return Panel(lowerPanelFromWorld, topPanel.mWidth, topPanel.mHeight,
|
||||
kDefaultLowerPanelScaleFactor, XrVector2f{cropHoriz / 2.0f, 0.0f},
|
||||
XrVector2f{topPanel.mWidth - cropHoriz / 2.0f, topPanel.mHeight});
|
||||
|
@ -419,46 +420,75 @@ bool GameSurfaceLayer::GetRayIntersectionWithPanel(const XrVector3f& start,
|
|||
|
||||
void GameSurfaceLayer::SetTopPanelFromController(const XrVector3f& controllerPosition) {
|
||||
|
||||
static constexpr XrVector3f viewerPosition{0, 0, 0}; // Set viewer position
|
||||
static constexpr XrVector3f viewerPosition{0.0f, 0.0f, 0.0f}; // Set viewer position
|
||||
const float sphereRadius = XrMath::Vector3f::Length(
|
||||
mTopPanel.mPanelFromWorld.position - viewerPosition); // Set the initial distance of the
|
||||
|
||||
// window from the viewer
|
||||
static constexpr XrVector3f windowUpDirection{0, 1, 0}; // Y is up
|
||||
static constexpr XrVector3f windowUpDirection{0.0f, 1.0f, 0.0f}; // Y is up
|
||||
|
||||
const XrVector3f windowPosition =
|
||||
CalculatePanelPosition(viewerPosition, controllerPosition, sphereRadius);
|
||||
const XrQuaternionf windowRotation =
|
||||
CalculatePanelRotation(windowPosition, viewerPosition, windowUpDirection);
|
||||
if (windowPosition.y <
|
||||
(mLowerPanel.mPanelFromWorld.position.y + kDistanceBetweenPanelsInMeters)) {
|
||||
if (XrMath::Vector3f::LengthSq(windowPosition - mLowerPanel.mPanelFromWorld.position) <
|
||||
XrMath::Vector3f::LengthSq(mTopPanel.mInitialPose.position -
|
||||
mLowerPanel.mInitialPose.position)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (XrMath::Quatf::GetPitchInRadians(windowRotation) > MATH_FLOAT_PI / 3.0f) { return; }
|
||||
|
||||
mTopPanel.mPanelFromWorld = XrPosef{windowRotation, windowPosition};
|
||||
}
|
||||
|
||||
// Goal is to rotate the lower panel to face the user, but with an initial bias of 45 degrees.
|
||||
// The result is the lower panel being slightly tilted away from the user compared to the top panel,
|
||||
// but comfortably readable at any angle.
|
||||
// The rotational offset is done so that the top+bottom text comfortably fit into the user's FOV
|
||||
// at high angles, so the user isn't craning their neck while reclining.
|
||||
void GameSurfaceLayer::SetLowerPanelFromController(const XrVector3f& controllerPosition) {
|
||||
constexpr XrVector3f viewerPosition{0.0f, 0.0f, 0.0f}; // Viewer position at origin
|
||||
constexpr XrVector3f windowUpDirection{0.0f, 1.0f, 0.0f}; // Y is up
|
||||
constexpr float pitchAdjustmentFactor = 0.5f;
|
||||
|
||||
static constexpr XrVector3f viewerPosition{0, 0, 0}; // Set viewer position
|
||||
const float sphereRadius = XrMath::Vector3f::Length(
|
||||
mLowerPanel.mPanelFromWorld.position - viewerPosition); // Set the initial distance of the
|
||||
|
||||
// window from the viewer
|
||||
static constexpr XrVector3f windowUpDirection{0, 1, 0}; // Y is up
|
||||
// Calculate sphere radius based on panel position to viewer
|
||||
const float sphereRadius =
|
||||
XrMath::Vector3f::Length(mLowerPanel.mPanelFromWorld.position - viewerPosition);
|
||||
|
||||
// Calculate new window position based on controller and sphere radius
|
||||
const XrVector3f windowPosition =
|
||||
CalculatePanelPosition(viewerPosition, controllerPosition, sphereRadius);
|
||||
const XrQuaternionf windowRotation =
|
||||
CalculatePanelRotation(windowPosition, viewerPosition, windowUpDirection);
|
||||
if (windowPosition.y >
|
||||
(mTopPanel.mPanelFromWorld.position.y - kDistanceBetweenPanelsInMeters)) {
|
||||
|
||||
// Limit vertical range to prevent the window from being too close to the viewer or the top
|
||||
// panel.
|
||||
if (windowPosition.z >= -0.5f ||
|
||||
XrMath::Vector3f::LengthSq(mTopPanel.mPanelFromWorld.position - windowPosition) <
|
||||
XrMath::Vector3f::LengthSq(mTopPanel.mInitialPose.position -
|
||||
mLowerPanel.mInitialPose.position)) {
|
||||
return;
|
||||
}
|
||||
if (XrMath::Quatf::GetPitchInRadians(windowRotation) > MATH_FLOAT_PI / 3.0f) { return; }
|
||||
|
||||
mLowerPanel.mPanelFromWorld = XrPosef{windowRotation, windowPosition};
|
||||
// Calculate the base rotation of the panel to face the user
|
||||
const XrQuaternionf baseRotation =
|
||||
CalculatePanelRotation(windowPosition, viewerPosition, windowUpDirection);
|
||||
|
||||
// Calculate pitch adjustment based on vertical displacement from initial position
|
||||
const float verticalDisplacement = windowPosition.y - mLowerPanel.mInitialPose.position.y;
|
||||
// Arbitrary factor, chosen based on what change-in-pitch felt best
|
||||
// A higher factor will make the window pitch more aggressively
|
||||
const float pitchAdjustment = verticalDisplacement * pitchAdjustmentFactor;
|
||||
// Clamp the new pitch to reasonable bounds (-45 to 90 degrees)
|
||||
const float newPitchRadians =
|
||||
std::clamp(-std::abs(kInitialLowerPanelPitchInRadians + pitchAdjustment),
|
||||
kInitialLowerPanelPitchInRadians, MATH_FLOAT_PI / 2.0f);
|
||||
|
||||
// Construct a quaternion for the pitch adjustment
|
||||
const XrQuaternionf pitchAdjustmentQuat =
|
||||
XrMath::FromAxisAngle({1.0f, 0.0f, 0.0f}, newPitchRadians / 2.0f);
|
||||
|
||||
// Combine the base rotation with the pitch adjustment
|
||||
mLowerPanel.mPanelFromWorld = {baseRotation * pitchAdjustmentQuat, windowPosition};
|
||||
}
|
||||
|
||||
static constexpr float kThumbstickSpeed = 0.05f;
|
||||
|
|
|
@ -78,7 +78,8 @@ public:
|
|||
, mPanelFromWorld(pose)
|
||||
, mWidth(width)
|
||||
, mHeight(height)
|
||||
, mScaleFactor(scaleFactor) {}
|
||||
, mScaleFactor(scaleFactor)
|
||||
, mInitialPose(pose) {}
|
||||
Panel(const XrPosef& pose, const float width, const float height, const float scaleFactor)
|
||||
: Panel(pose, width, height, scaleFactor, {0, 0}, {width, height}) {}
|
||||
|
||||
|
@ -89,10 +90,11 @@ public:
|
|||
XrVector2f mMin;
|
||||
XrVector2f mMax;
|
||||
} mClickBounds;
|
||||
XrPosef mPanelFromWorld;
|
||||
const float mWidth;
|
||||
const float mHeight;
|
||||
const float mScaleFactor;
|
||||
XrPosef mPanelFromWorld;
|
||||
const float mWidth;
|
||||
const float mHeight;
|
||||
const float mScaleFactor;
|
||||
const XrPosef mInitialPose;
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -158,19 +160,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);
|
||||
void SetTopPanelFromThumbstick(const float thumbstickY);
|
||||
XrPosef SetLowerPanelFromThumbstick(const float thumbstickY);
|
||||
void SetLowerPanelFromController(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);
|
||||
void SetLowerPanelFromController(const XrVector3f& controllerPosition);
|
||||
XrPosef GetTopPanelFromHeadPose(uint32_t eye, const XrPosef& headPose);
|
||||
const XrPosef& GetLowerPanelPose() const { return mLowerPanel.mPanelFromWorld; }
|
||||
|
||||
private:
|
||||
|
|
|
@ -274,12 +274,12 @@ private:
|
|||
|
||||
mRibbonLayer = std::make_unique<UILayer>(
|
||||
"org/citra/citra_emu/vr/ui/VrRibbonLayer", XrVector3f{0, -0.75f, -1.51f},
|
||||
XrMath::Quatf::FromEuler(0.0f, -MATH_FLOAT_PI / 4.0f, 0.0f), jni, mActivityObject,
|
||||
XrMath::Quatf::FromEuler(-MATH_FLOAT_PI / 4.0f, 0.0f, 0.0f), jni, mActivityObject,
|
||||
gOpenXr->mSession);
|
||||
|
||||
mKeyboardLayer = std::make_unique<UILayer>(
|
||||
"org/citra/citra_emu/vr/ui/VrKeyboardLayer", XrVector3f{0, -0.4f, -0.5f},
|
||||
XrMath::Quatf::FromEuler(0.0f, -MATH_FLOAT_PI / 4.0f, 0.0f), jni, mActivityObject,
|
||||
XrMath::Quatf::FromEuler(-MATH_FLOAT_PI / 4.0f, 0.0f, 0.0f), jni, mActivityObject,
|
||||
gOpenXr->mSession);
|
||||
|
||||
mErrorMessageLayer = std::make_unique<UILayer>(
|
||||
|
@ -427,7 +427,7 @@ private:
|
|||
Core::System::GetInstance().GPU().Renderer().Rasterizer()) {
|
||||
if (VRSettings::values.vr_immersive_mode == 0 ||
|
||||
// If in normal immersive mode then look down for the lower panel to reveal
|
||||
// itself (for some reason the Roll function returns pitch)
|
||||
// itself
|
||||
(VRSettings::values.vr_immersive_mode == 1 &&
|
||||
XrMath::Quatf::GetPitchInRadians(gOpenXr->headLocation.pose.orientation) <
|
||||
-MATH_FLOAT_PI / 8.0f) ||
|
||||
|
@ -668,7 +668,7 @@ private:
|
|||
mInputStateFrame.mIsHandActive[mInputStateFrame.mPreferredHand];
|
||||
|
||||
static bool sIsLowerPanelBeingPositioned = false;
|
||||
const bool wasLowerPanelBeingPositioned = sIsLowerPanelBeingPositioned;
|
||||
const bool wasLowerPanelBeingPositioned = sIsLowerPanelBeingPositioned;
|
||||
|
||||
sIsLowerPanelBeingPositioned &=
|
||||
appState.mLowerMenuType == LowerMenuType::POSITIONAL_MENU &&
|
||||
|
@ -709,9 +709,10 @@ private:
|
|||
// applicable panels
|
||||
|
||||
// Lock ribbon in place when placement is complete
|
||||
const bool needRibbonUpdate = !sIsLowerPanelBeingPositioned && wasLowerPanelBeingPositioned;
|
||||
const bool needRibbonUpdate =
|
||||
!sIsLowerPanelBeingPositioned && wasLowerPanelBeingPositioned;
|
||||
if (needRibbonUpdate) {
|
||||
mRibbonLayer->SetPanelWithPose(mGameSurfaceLayer->GetLowerPanelPose());
|
||||
mRibbonLayer->SetPanelWithPose(mGameSurfaceLayer->GetLowerPanelPose());
|
||||
}
|
||||
|
||||
if (!shouldRenderCursor) {
|
||||
|
@ -723,7 +724,7 @@ private:
|
|||
mGameSurfaceLayer->SetLowerPanelFromController(
|
||||
XrVector3f{0, cursorPose3d.position.y, cursorPose3d.position.z});
|
||||
|
||||
sIsLowerPanelBeingPositioned = true;
|
||||
sIsLowerPanelBeingPositioned = true;
|
||||
} else if (appState.mLowerMenuType == LowerMenuType::MAIN_MENU) {
|
||||
if (triggerState.currentState == 0 && triggerState.changedSinceLastSync) {
|
||||
jni->CallVoidMethod(mActivityObject, mSendClickToWindowMethodID,
|
||||
|
@ -1110,8 +1111,6 @@ private:
|
|||
|
||||
class AppState {
|
||||
public:
|
||||
bool ShouldShowLowerPanel() const { return mLowerMenuType == LowerMenuType::MAIN_MENU; }
|
||||
|
||||
LowerMenuType mLowerMenuType = LowerMenuType::MAIN_MENU;
|
||||
|
||||
bool mIsKeyboardActive = false;
|
||||
|
|
Loading…
Reference in a new issue