mirror of
https://github.com/amwatson/CitraVR.git
synced 2024-09-20 03:11:40 +02:00
add horizontal freedom and reset options to positional menu
This commit is contained in:
parent
5fe11a8be5
commit
3f90b3cc6c
10 changed files with 133 additions and 69 deletions
|
@ -434,10 +434,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
|
|||
override fun onResume() {
|
||||
super.onResume()
|
||||
Choreographer.getInstance().postFrameCallback(this)
|
||||
if (NativeLibrary.isRunning()) {
|
||||
NativeLibrary.unPauseEmulation()
|
||||
return
|
||||
}
|
||||
|
||||
if (DirectoryInitialization.areCitraDirectoriesReady()) {
|
||||
emulationState.run(emulationActivity.isActivityRecreated)
|
||||
|
@ -447,9 +443,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
|
|||
}
|
||||
|
||||
override fun onPause() {
|
||||
if (NativeLibrary.isRunning()) {
|
||||
emulationState.pause()
|
||||
}
|
||||
Choreographer.getInstance().removeFrameCallback(this)
|
||||
super.onPause()
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package org.citra.citra_emu.vr.ui
|
|||
import android.view.KeyEvent
|
||||
import android.view.View
|
||||
import android.widget.Button
|
||||
import android.widget.ToggleButton
|
||||
import org.citra.citra_emu.NativeLibrary
|
||||
import org.citra.citra_emu.R
|
||||
import org.citra.citra_emu.vr.VrActivity
|
||||
|
@ -14,11 +15,13 @@ class VrRibbonLayer(activity: VrActivity) : VrUILayer(activity, R.layout.vr_ribb
|
|||
MAIN(R.id.main_panel),
|
||||
POSITION(R.id.position_panel)
|
||||
}
|
||||
|
||||
private var menuTypeCurrent: MenuType = MenuType.MAIN
|
||||
|
||||
override fun onSurfaceCreated() {
|
||||
super.onSurfaceCreated()
|
||||
initializeMainView()
|
||||
initializeMainPanel()
|
||||
initializePositionalPanel()
|
||||
}
|
||||
|
||||
fun switchMenus(menuTypeNew: MenuType) {
|
||||
|
@ -33,19 +36,37 @@ class VrRibbonLayer(activity: VrActivity) : VrUILayer(activity, R.layout.vr_ribb
|
|||
VrMessageQueue.post(VrMessageQueue.MessageType.CHANGE_LOWER_MENU, 1)
|
||||
}
|
||||
|
||||
fun initializeMainView() {
|
||||
private fun initializePositionalPanel() {
|
||||
val horizontalLockToggle = window?.findViewById<ToggleButton>(R.id.horizontalAxisToggle)
|
||||
horizontalLockToggle?.setOnCheckedChangeListener { _, isChecked ->
|
||||
VrMessageQueue.post(VrMessageQueue.MessageType.CHANGE_LOCK_HORIZONTAL_AXIS, if (isChecked) 1 else 0)
|
||||
}
|
||||
val btnReset = window?.findViewById<Button>(R.id.btnReset)
|
||||
btnReset?.setOnClickListener { _ ->
|
||||
VrMessageQueue.post(VrMessageQueue.MessageType.RESET_PANEL_POSITIONS, 0)
|
||||
false
|
||||
}
|
||||
|
||||
horizontalLockToggle?.isChecked = true;
|
||||
VrMessageQueue.post(VrMessageQueue.MessageType.CHANGE_LOCK_HORIZONTAL_AXIS, if (horizontalLockToggle?.isChecked == true) 1 else 0)
|
||||
}
|
||||
|
||||
private fun initializeMainPanel() {
|
||||
window?.findViewById<Button>(R.id.buttonSelect)?.setOnTouchListener { _, motionEvent ->
|
||||
val action: Int = when (motionEvent.action) {
|
||||
KeyEvent.ACTION_DOWN -> {
|
||||
// Normal key events.
|
||||
NativeLibrary.ButtonState.PRESSED
|
||||
}
|
||||
|
||||
KeyEvent.ACTION_UP -> NativeLibrary.ButtonState.RELEASED
|
||||
else -> return@setOnTouchListener false
|
||||
}
|
||||
NativeLibrary.onGamePadEvent(NativeLibrary.TouchScreenDevice,
|
||||
NativeLibrary.onGamePadEvent(
|
||||
NativeLibrary.TouchScreenDevice,
|
||||
NativeLibrary.ButtonType.BUTTON_SELECT,
|
||||
action)
|
||||
action
|
||||
)
|
||||
false
|
||||
}
|
||||
window?.findViewById<Button>(R.id.buttonStart)?.setOnTouchListener { _, motionEvent ->
|
||||
|
@ -54,12 +75,15 @@ class VrRibbonLayer(activity: VrActivity) : VrUILayer(activity, R.layout.vr_ribb
|
|||
// Normal key events.
|
||||
NativeLibrary.ButtonState.PRESSED
|
||||
}
|
||||
|
||||
KeyEvent.ACTION_UP -> NativeLibrary.ButtonState.RELEASED
|
||||
else -> return@setOnTouchListener false
|
||||
}
|
||||
NativeLibrary.onGamePadEvent(NativeLibrary.TouchScreenDevice,
|
||||
NativeLibrary.onGamePadEvent(
|
||||
NativeLibrary.TouchScreenDevice,
|
||||
NativeLibrary.ButtonType.BUTTON_START,
|
||||
action)
|
||||
action
|
||||
)
|
||||
false
|
||||
}
|
||||
window?.findViewById<Button>(R.id.buttonExit)?.setOnTouchListener { _, motionEvent ->
|
||||
|
@ -79,7 +103,8 @@ class VrRibbonLayer(activity: VrActivity) : VrUILayer(activity, R.layout.vr_ribb
|
|||
}
|
||||
|
||||
btnPrev?.setOnClickListener { _ ->
|
||||
val prevIdx = (menuTypeCurrent.ordinal - 1 + MenuType.values().size) % MenuType.values().size
|
||||
val prevIdx =
|
||||
(menuTypeCurrent.ordinal - 1 + MenuType.values().size) % MenuType.values().size
|
||||
switchMenus(MenuType.values()[prevIdx])
|
||||
if ((prevIdx - 1) <= 0)
|
||||
btnPrev.visibility = View.INVISIBLE
|
||||
|
|
|
@ -6,7 +6,9 @@ object VrMessageQueue {
|
|||
SHOW_KEYBOARD(0),
|
||||
SHOW_ERROR_MESSAGE(1),
|
||||
EXIT_NEEDED(2),
|
||||
CHANGE_LOWER_MENU(3)
|
||||
CHANGE_LOWER_MENU(3),
|
||||
CHANGE_LOCK_HORIZONTAL_AXIS(4),
|
||||
RESET_PANEL_POSITIONS(5)
|
||||
}
|
||||
@JvmStatic
|
||||
fun post(messageType: MessageType, payload: Long) {
|
||||
|
|
|
@ -419,25 +419,23 @@ bool GameSurfaceLayer::GetRayIntersectionWithPanel(const XrVector3f& start,
|
|||
|
||||
void GameSurfaceLayer::SetTopPanelFromController(const XrVector3f& controllerPosition) {
|
||||
|
||||
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
|
||||
constexpr XrVector3f viewerPosition{0.0f, 0.0f, 0.0f}; // Set viewer position
|
||||
constexpr XrVector3f windowUpDirection{0.0f, 1.0f, 0.0f}; // Y is up
|
||||
|
||||
// window from the viewer
|
||||
static constexpr XrVector3f windowUpDirection{0.0f, 1.0f, 0.0f}; // Y is up
|
||||
|
||||
const XrVector3f windowPosition =
|
||||
// Set the initial distance of the window from the viewer.
|
||||
const float sphereRadius =
|
||||
XrMath::Vector3f::Length(mTopPanel.mPanelFromWorld.position - viewerPosition);
|
||||
XrVector3f windowPosition =
|
||||
CalculatePanelPosition(viewerPosition, controllerPosition, sphereRadius);
|
||||
const XrQuaternionf windowRotation =
|
||||
CalculatePanelRotation(windowPosition, viewerPosition, windowUpDirection);
|
||||
if (windowPosition.z >= -0.5f) { return; }
|
||||
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; }
|
||||
|
||||
const XrQuaternionf windowRotation =
|
||||
CalculatePanelRotation(windowPosition, viewerPosition, windowUpDirection);
|
||||
mTopPanel.mPanelFromWorld = XrPosef{windowRotation, windowPosition};
|
||||
}
|
||||
|
||||
|
@ -525,6 +523,12 @@ XrPosef GameSurfaceLayer::GetTopPanelFromHeadPose(uint32_t eye, const XrPosef& h
|
|||
return XrPosef{headPose.orientation, panelPosition};
|
||||
}
|
||||
|
||||
void GameSurfaceLayer::ResetPanelPositions() {
|
||||
|
||||
mTopPanel.mPanelFromWorld = mTopPanel.mInitialPose;
|
||||
mLowerPanel.mPanelFromWorld = mLowerPanel.mInitialPose;
|
||||
}
|
||||
|
||||
// based on thumbstick, modify the depth of the top panel
|
||||
// Next error code: -2
|
||||
int32_t GameSurfaceLayer::Init(const XrSession& session, const jobject activityObject) {
|
||||
|
|
|
@ -170,10 +170,10 @@ public:
|
|||
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; }
|
||||
void ResetPanelPositions();
|
||||
|
||||
private:
|
||||
int Init(const XrSession& session, const jobject activityObject);
|
||||
|
|
|
@ -18,6 +18,11 @@ License : Licensed under GPLv3 or any later version.
|
|||
#define NUM_EYES 2 // this will never change, it just helps people know what we mean.
|
||||
#endif
|
||||
|
||||
// closest (to view origin) z-depth to put a panel
|
||||
#ifndef Z_MAX
|
||||
#define Z_MAX -0.5f
|
||||
#endif
|
||||
|
||||
#define BAIL_ON_COND(cond, errorStr, returnCode) \
|
||||
do { \
|
||||
if (cond) { \
|
||||
|
|
|
@ -31,7 +31,9 @@ struct Message {
|
|||
SHOW_KEYBOARD = 0, // payload 0 = hide keyboard, 1 = show keyboard
|
||||
SHOW_ERROR_MESSAGE = 1, // payload 0 = show error message, 1 = hide error message
|
||||
EXIT_NEEDED = 2, // payload ignored
|
||||
CHANGE_LOWER_MENU = 3 // payload indicates menu ID
|
||||
CHANGE_LOWER_MENU = 3, // payload indicates menu ID
|
||||
CHANGE_LOCK_HORIZONTAL_AXIS = 4, // payload 0 = unlock, 1 = lock
|
||||
RESET_PANEL_POSITIONS = 5, // payload ignored
|
||||
};
|
||||
|
||||
Message() {}
|
||||
|
|
|
@ -90,6 +90,10 @@ public:
|
|||
static XrVector3f Cross(const XrVector3f& a, const XrVector3f& b) {
|
||||
return XrVector3f{a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x};
|
||||
}
|
||||
|
||||
static float Dot(const XrVector3f& a, const XrVector3f& b) {
|
||||
return a.x * b.x + a.y * b.y + a.z * b.z;
|
||||
}
|
||||
};
|
||||
|
||||
class Matrixf {
|
||||
|
|
|
@ -229,6 +229,12 @@ public:
|
|||
if (appState.mIsXrSessionActive) {
|
||||
Frame(jni, appState);
|
||||
} else {
|
||||
// FIXME: currently, the way this is set up, some messages can be discarded if they
|
||||
// aren't processed on the next frame.
|
||||
// This currently requires that all AppState state-related events be handled in
|
||||
// HandleStateChanges. and not in the frame for 100% correctness (consequence is
|
||||
// losing messages on unmount). This is possibly solved by handling MessageQueue
|
||||
// events inside the frame call.
|
||||
// TODO should block here
|
||||
mFrameIndex = 0;
|
||||
}
|
||||
|
@ -741,7 +747,8 @@ private:
|
|||
appState.mLowerMenuType == LowerMenuType::POSITIONAL_MENU &&
|
||||
triggerState.currentState) {
|
||||
mGameSurfaceLayer->SetLowerPanelFromController(
|
||||
XrVector3f{0, cursorPose3d.position.y, cursorPose3d.position.z});
|
||||
{appState.mIsHorizontalAxisLocked ? 0.0f : cursorPose3d.position.x,
|
||||
cursorPose3d.position.y, cursorPose3d.position.z});
|
||||
|
||||
sIsLowerPanelBeingPositioned = true;
|
||||
} else if (appState.mLowerMenuType == LowerMenuType::MAIN_MENU) {
|
||||
|
@ -759,7 +766,7 @@ private:
|
|||
}
|
||||
}
|
||||
|
||||
// 5. Hit test the top panel if positional menu is active.
|
||||
// 5. Top panel (only if positional menu is active)
|
||||
if (!shouldRenderCursor &&
|
||||
appState.mLowerMenuType == LowerMenuType::POSITIONAL_MENU) {
|
||||
shouldRenderCursor = mGameSurfaceLayer->GetRayIntersectionWithPanelTopPanel(
|
||||
|
@ -769,8 +776,9 @@ private:
|
|||
if (shouldRenderCursor && triggerState.currentState) {
|
||||
// null out X component -- screen should stay
|
||||
// center
|
||||
mGameSurfaceLayer->SetTopPanelFromController(
|
||||
XrVector3f{0, cursorPose3d.position.y, cursorPose3d.position.z});
|
||||
mGameSurfaceLayer->SetTopPanelFromController(XrVector3f{
|
||||
appState.mIsHorizontalAxisLocked ? 0.0f : cursorPose3d.position.x,
|
||||
cursorPose3d.position.y, cursorPose3d.position.z});
|
||||
// If trigger is pressed, thumbstick controls
|
||||
// the depth
|
||||
const XrActionStateVector2f& thumbstickState =
|
||||
|
@ -853,11 +861,17 @@ private:
|
|||
}
|
||||
|
||||
void HandleStateChanges(JNIEnv* jni, AppState& newState) const {
|
||||
const bool shouldPauseEmulation = !newState.mHasFocus || newState.mShouldShowErrorMessage ||
|
||||
{
|
||||
const bool shouldPauseEmulation =
|
||||
!newState.mHasFocus || newState.mShouldShowErrorMessage ||
|
||||
newState.mLowerMenuType == LowerMenuType::POSITIONAL_MENU;
|
||||
if (shouldPauseEmulation != mLastAppState.mIsEmulationPaused) {
|
||||
ALOGI("State change: Emulation paused: {} -> {}", mLastAppState.mIsEmulationPaused,
|
||||
newState.mIsEmulationPaused);
|
||||
ALOGI("State change: Emulation paused: {} -> {} (F={}, E={}, MP={})",
|
||||
mLastAppState.mIsEmulationPaused, shouldPauseEmulation, newState.mHasFocus,
|
||||
newState.mShouldShowErrorMessage,
|
||||
newState.mLowerMenuType == LowerMenuType::POSITIONAL_MENU ? "P"
|
||||
: newState.mLowerMenuType == LowerMenuType::MAIN_MENU ? "M"
|
||||
: "U");
|
||||
if (shouldPauseEmulation) {
|
||||
PauseEmulation(jni);
|
||||
newState.mIsEmulationPaused = true;
|
||||
|
@ -868,6 +882,17 @@ private:
|
|||
}
|
||||
}
|
||||
|
||||
if (newState.mNumPanelResets > mLastAppState.mNumPanelResets) {
|
||||
mGameSurfaceLayer->ResetPanelPositions();
|
||||
mRibbonLayer->SetPanelWithPose(mGameSurfaceLayer->GetLowerPanelPose());
|
||||
}
|
||||
|
||||
if (newState.mIsHorizontalAxisLocked && !mLastAppState.mIsHorizontalAxisLocked) {
|
||||
mGameSurfaceLayer->ResetPanelPositions();
|
||||
mRibbonLayer->SetPanelWithPose(mGameSurfaceLayer->GetLowerPanelPose());
|
||||
}
|
||||
}
|
||||
|
||||
void OXRPollEvents(JNIEnv* jni, AppState& newAppState) const {
|
||||
XrEventDataBuffer eventDataBuffer = {};
|
||||
|
||||
|
@ -1073,12 +1098,23 @@ private:
|
|||
}
|
||||
case Message::Type::CHANGE_LOWER_MENU: {
|
||||
newAppState.mLowerMenuType = static_cast<LowerMenuType>(message.mPayload);
|
||||
ALOGI("Received CHANGE_LOWER_MENU message: {}, state change {} -> {}",
|
||||
ALOGD("Received CHANGE_LOWER_MENU message: {}, state change {} -> {}",
|
||||
message.mPayload, mLastAppState.mLowerMenuType,
|
||||
newAppState.mLowerMenuType);
|
||||
break;
|
||||
}
|
||||
|
||||
case Message::Type::CHANGE_LOCK_HORIZONTAL_AXIS: {
|
||||
ALOGD("Received CHANGE_LOCK_HORIZONTAL_AXIS message: {}, state change {} -> {}",
|
||||
message.mPayload, mLastAppState.mIsHorizontalAxisLocked,
|
||||
message.mPayload == 1);
|
||||
newAppState.mIsHorizontalAxisLocked = message.mPayload == 1;
|
||||
break;
|
||||
}
|
||||
case Message::Type::RESET_PANEL_POSITIONS: {
|
||||
ALOGD("Received RESET_PANEL_POSITIONS message");
|
||||
newAppState.mNumPanelResets++;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
ALOGE("Unknown message type: %d", message.mType);
|
||||
break;
|
||||
|
@ -1103,6 +1139,8 @@ private:
|
|||
class AppState {
|
||||
public:
|
||||
LowerMenuType mLowerMenuType = LowerMenuType::MAIN_MENU;
|
||||
int32_t mNumPanelResets = 0;
|
||||
bool mIsHorizontalAxisLocked = true;
|
||||
|
||||
bool mIsKeyboardActive = false;
|
||||
bool mShouldShowErrorMessage = false;
|
||||
|
|
|
@ -3,7 +3,9 @@
|
|||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/position_panel"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
android:layout_height="wrap_content"
|
||||
android:theme="@style/Theme.Material3.DayNight"
|
||||
>
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="1000dp"
|
||||
|
@ -35,35 +37,24 @@
|
|||
app:layout_constraintTop_toTopOf="@+id/lowerPanelPlaceHolder">
|
||||
|
||||
<Button
|
||||
android:id="@+id/buttonSelect"
|
||||
android:id="@+id/btnReset"
|
||||
style="@style/VrRibbonButtonStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:text="@string/button_select" />
|
||||
android:text="Reset" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/buttonStart"
|
||||
style="@style/VrRibbonButtonStyle"
|
||||
android:layout_width="match_parent"
|
||||
<ToggleButton
|
||||
android:id="@+id/horizontalAxisToggle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/button_start" />
|
||||
android:text="Lock Horizontal Axis"
|
||||
/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/buttonExit"
|
||||
style="@style/VrRibbonButtonStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/emulation_close_game" />
|
||||
</LinearLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
Loading…
Reference in a new issue