split ribbon into a specific ribbon layer and give it methods for setting position

This commit is contained in:
amwatson 2024-04-09 21:18:57 -05:00
parent d012252cb8
commit 84a2725906
8 changed files with 134 additions and 30 deletions

View file

@ -38,6 +38,7 @@ add_library(citra-android SHARED
vr/layers/CursorLayer.cpp
vr/layers/GameSurfaceLayer.cpp
vr/layers/PassthroughLayer.cpp
vr/layers/RibbonLayer.cpp
vr/layers/UILayer.cpp
vr/utils/JniUtils.cpp
vr/utils/JniClassNames.cpp

View file

@ -593,3 +593,7 @@ void GameSurfaceLayer::CreateSwapchain() {
mSwapchain.mWidth = xsci.width;
mSwapchain.mHeight = xsci.height;
}
void GameSurfaceLayer::SetLowerPanelWithPose(const XrPosef& pose) {
mLowerPanel.mPanelFromWorld = pose;
}

View file

@ -172,8 +172,9 @@ public:
void SetTopPanelFromThumbstick(const float thumbstickY);
void SetLowerPanelFromController(const XrVector3f& controllerPosition);
XrPosef GetTopPanelFromHeadPose(uint32_t eye, const XrPosef& headPose);
const XrPosef& GetLowerPanelPose() const { return mLowerPanel.mPanelFromWorld; }
void ResetPanelPositions();
const XrPosef& GetLowerPanelPose() const { return mLowerPanel.mPanelFromWorld; }
void SetLowerPanelWithPose(const XrPosef& pose);
private:
int Init(const XrSession& session, const jobject activityObject);

View file

@ -0,0 +1,91 @@
#include "RibbonLayer.h"
#include "../utils/LogUtils.h"
#include "../utils/XrMath.h"
namespace {
constexpr float kInitialLowerPanelPitchInRadians = -MATH_FLOAT_PI / 4.0f; // -45 degrees in radians
XrVector3f CalculatePanelPosition(const XrVector3f& viewerPosition,
const XrVector3f& controllerPosition,
float sphereRadius) {
// Vector from viewer to controller
XrVector3f viewerToController = controllerPosition - viewerPosition;
XrMath::Vector3f::Normalize(viewerToController);
// Calculate position on the sphere's surface
return viewerPosition + sphereRadius * viewerToController;
}
XrQuaternionf CalculatePanelRotation(const XrVector3f& windowPosition,
const XrVector3f& viewerPosition,
const XrVector3f& upDirection) {
// Compute forward direction (normalized)
XrVector3f forward = viewerPosition - windowPosition;
XrMath::Vector3f::Normalize(forward);
// Compute right direction (normalized)
XrVector3f right = XrMath::Vector3f::Cross(upDirection, forward);
XrMath::Vector3f::Normalize(right);
// Recompute up direction (normalized) to ensure orthogonality
const XrVector3f up = XrMath::Vector3f::Cross(forward, right);
return XrMath::Quatf::FromThreeVectors(forward, up, right);
}
} // anonymous namespace
RibbonLayer::RibbonLayer(const XrVector3f&& position, const XrQuaternionf&& orientation,
JNIEnv* jni, jobject activityObject, const XrSession& session)
: UILayer("org/citra/citra_emu/vr/ui/VrRibbonLayer", std::move(position),
std::move(orientation), jni, activityObject, session)
, mInitialPose({orientation, position}) {
mIsMenuBackgroundSelectedMethodId =
jni->GetMethodID(GetVrUILayerClass(), "isMenuBackgroundSelected", "()Z");
if (mIsMenuBackgroundSelectedMethodId == nullptr) {
FAIL("Could not find isMenuBackgroundSelected()");
}
}
bool RibbonLayer::IsMenuBackgroundSelected() const {
return GetEnv()->CallBooleanMethod(GetVrUILayerObject(), mIsMenuBackgroundSelectedMethodId);
}
void RibbonLayer::SetPanelFromController(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;
// Calculate sphere radius based on panel position to viewer
const float sphereRadius = XrMath::Vector3f::Length(mPanelFromWorld.position - viewerPosition);
// Calculate new window position based on controller and sphere radius
const XrVector3f windowPosition =
CalculatePanelPosition(viewerPosition, controllerPosition, sphereRadius);
// Limit vertical range to prevent the window from being too close to the viewer or the top
// panel.
if (windowPosition.z >= -0.5f) { return; }
// 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 - 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::Quatf::FromAxisAngle({1.0f, 0.0f, 0.0f}, newPitchRadians / 2.0f);
// Combine the base rotation with the pitch adjustment
mPanelFromWorld = {baseRotation * pitchAdjustmentQuat, windowPosition};
}

View file

@ -0,0 +1,18 @@
#pragma once
#include "UILayer.h"
class RibbonLayer : public UILayer {
public:
RibbonLayer(const XrVector3f&& position, const XrQuaternionf&& orientation, JNIEnv* jni,
jobject activityObject, const XrSession& session);
bool IsMenuBackgroundSelected() const;
void SetPanelFromController(const XrVector3f& controllerPosition);
const XrPosef& GetPose() const { return mPanelFromWorld; }
private:
const XrPosef mInitialPose;
jmethodID mIsMenuBackgroundSelectedMethodId = nullptr;
};

View file

@ -162,8 +162,8 @@ XrVector2f GetDensityScaleForSize(const int32_t texWidth, const int32_t texHeigh
UILayer::UILayer(const std::string& className, const XrVector3f&& position,
const XrQuaternionf&& orientation, JNIEnv* env, jobject activityObject,
const XrSession& session)
: mSession(session)
, mPanelFromWorld(XrPosef{orientation, position})
: mPanelFromWorld(XrPosef{orientation, position})
, mSession(session)
, mEnv(env) {
const int32_t initializationStatus = Init(className, activityObject, position, session);
if (initializationStatus < 0) {
@ -214,7 +214,7 @@ bool UILayer::GetRayIntersectionWithPanel(const XrVector3f& start,
scale, start, end, result2d, result3d);
}
// Next error code: -9
// Next error code: -8
int32_t UILayer::Init(const std::string& className, const jobject activityObject,
const XrVector3f& position, const XrSession& session) {
mVrUILayerClass = JniUtils::GetGlobalClassReference(mEnv, activityObject, className.c_str());
@ -238,16 +238,7 @@ int32_t UILayer::Init(const std::string& className, const jobject activityObject
BAIL_ON_COND(mSendClickToUIMethodID == nullptr, "could not find sendClickToUI()", -6);
if (className == "org/citra/citra_emu/vr/ui/VrRibbonLayer") {
ALOGD("UILayer: using companion class");
mIsMenuBackgroundSelectedMethodId =
mEnv->GetMethodID(mVrUILayerClass, "isMenuBackgroundSelected", "()Z");
BAIL_ON_COND(mIsMenuBackgroundSelectedMethodId == nullptr,
"could not find isMenuBackgroundSelected()", -7);
}
BAIL_ON_ERR(CreateSwapchain(), -8);
BAIL_ON_ERR(CreateSwapchain(), -7);
return 0;
}
@ -336,7 +327,3 @@ void UILayer::SendClickToUI(const XrVector2f& pos2d, const int type) {
}
void UILayer::SetPanelWithPose(const XrPosef& pose) { mPanelFromWorld = pose; }
bool UILayer::IsMenuBackgroundSelected() const {
return mEnv->CallBooleanMethod(mVrUILayerObject, mIsMenuBackgroundSelectedMethodId);
}

View file

@ -125,7 +125,11 @@ public:
void SetPanelWithPose(const XrPosef& pose);
bool IsMenuBackgroundSelected() const;
protected:
jobject GetVrUILayerObject() const { return mVrUILayerObject; }
jclass GetVrUILayerClass() const { return mVrUILayerClass; }
JNIEnv* GetEnv() const { return mEnv; }
XrPosef mPanelFromWorld;
private:
int Init(const std::string& className, const jobject activityObject, const XrVector3f& position,
@ -140,8 +144,6 @@ private:
const XrSession mSession;
Swapchain mSwapchain;
XrPosef mPanelFromWorld;
//============================
// JNI objects
JNIEnv* mEnv = nullptr;
@ -156,8 +158,7 @@ private:
// the decorView representing an entire window, it's important to accont for
// the x, y offset of the view within the window, in case there are things
// like window decorations or status bars.
jmethodID mGetBoundsMethodID = nullptr;
jmethodID mSendClickToUIMethodID = nullptr;
jmethodID mSetSurfaceMethodId = nullptr;
jmethodID mIsMenuBackgroundSelectedMethodId = nullptr;
jmethodID mGetBoundsMethodID = nullptr;
jmethodID mSendClickToUIMethodID = nullptr;
jmethodID mSetSurfaceMethodId = nullptr;
};

View file

@ -17,6 +17,7 @@ License : Licensed under GPLv3 or any later version.
#include "layers/CursorLayer.h"
#include "layers/GameSurfaceLayer.h"
#include "layers/PassthroughLayer.h"
#include "layers/RibbonLayer.h"
#include "layers/UILayer.h"
#include "vr_settings.h"
@ -294,8 +295,8 @@ private:
XrVector3f{0, 0, -2.0f}, jni, mActivityObject, gOpenXr->mSession, resolutionFactor);
}
mRibbonLayer = std::make_unique<UILayer>(
"org/citra/citra_emu/vr/ui/VrRibbonLayer", XrVector3f{0, -0.75f, -1.51f},
mRibbonLayer = std::make_unique<RibbonLayer>(
XrVector3f{0, -0.75f, -1.51f},
XrMath::Quatf::FromEuler(-MATH_FLOAT_PI / 4.0f, 0.0f, 0.0f), jni, mActivityObject,
gOpenXr->mSession);
@ -753,10 +754,10 @@ private:
(shouldRenderCursor && mRibbonLayer->IsMenuBackgroundSelected())) &&
triggerState.currentState) {
mGameSurfaceLayer->SetLowerPanelFromController(
mRibbonLayer->SetPanelFromController(
{appState.mIsHorizontalAxisLocked ? 0.0f : cursorPose3d.position.x,
cursorPose3d.position.y, cursorPose3d.position.z});
mRibbonLayer->SetPanelWithPose(mGameSurfaceLayer->GetLowerPanelPose());
mGameSurfaceLayer->SetLowerPanelWithPose(mRibbonLayer->GetPose());
sIsLowerPanelBeingPositioned = true;
}
@ -1155,7 +1156,7 @@ private:
std::unique_ptr<GameSurfaceLayer> mGameSurfaceLayer;
std::unique_ptr<PassthroughLayer> mPassthroughLayer;
std::unique_ptr<UILayer> mKeyboardLayer;
std::unique_ptr<UILayer> mRibbonLayer;
std::unique_ptr<RibbonLayer> mRibbonLayer;
std::unique_ptr<InputStateStatic> mInputStateStatic;
InputStateFrame mInputStateFrame;