From 60f320bc07263527af94424107ffbc5abc972801 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Sat, 16 Dec 2023 14:04:25 +0000 Subject: [PATCH] android - add motion support --- src/LibRyujinx/Android/JniExportedMethods.cs | 14 +++ src/LibRyujinx/LibRyujinx.Input.cs | 41 ++++++- .../main/java/org/ryujinx/android/GameHost.kt | 3 +- .../java/org/ryujinx/android/MainActivity.kt | 7 ++ .../ryujinx/android/MotionSensorManager.kt | 111 ++++++++++++++++++ .../android/PhysicalControllerManager.kt | 7 +- .../java/org/ryujinx/android/RyujinxNative.kt | 2 + .../android/viewmodels/MainViewModel.kt | 6 + 8 files changed, 187 insertions(+), 4 deletions(-) create mode 100644 src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/MotionSensorManager.kt diff --git a/src/LibRyujinx/Android/JniExportedMethods.cs b/src/LibRyujinx/Android/JniExportedMethods.cs index 615d6d561..6a110ba7c 100644 --- a/src/LibRyujinx/Android/JniExportedMethods.cs +++ b/src/LibRyujinx/Android/JniExportedMethods.cs @@ -553,6 +553,20 @@ namespace LibRyujinx SetButtonReleased((GamepadButtonInputId)(int)button, id); } + [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputSetAccelerometerData")] + public static void JniSetAccelerometerData(JEnvRef jEnv, JObjectLocalRef jObj, JFloat x, JFloat y, JFloat z, JInt id) + { + var accel = new Vector3(x, y, z); + SetAccelerometerData(accel, id); + } + + [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputSetGyroData")] + public static void JniSetGyroData(JEnvRef jEnv, JObjectLocalRef jObj, JFloat x, JFloat y, JFloat z, JInt id) + { + var gryo = new Vector3(x, y, z); + SetGryoData(gryo, id); + } + [UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_inputSetStickAxis")] public static void JniSetStickAxis(JEnvRef jEnv, JObjectLocalRef jObj, JInt stick, JFloat x, JFloat y, JInt id) { diff --git a/src/LibRyujinx/LibRyujinx.Input.cs b/src/LibRyujinx/LibRyujinx.Input.cs index dc1c1f6d2..89fb04a2d 100644 --- a/src/LibRyujinx/LibRyujinx.Input.cs +++ b/src/LibRyujinx/LibRyujinx.Input.cs @@ -1,4 +1,4 @@ -using DiscordRPC; +using DiscordRPC; using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration.Hid; using Ryujinx.Common.Configuration.Hid.Controller; @@ -83,6 +83,16 @@ namespace LibRyujinx _gamepadDriver?.SetButtonReleased(button, id); } + public static void SetAccelerometerData(Vector3 accel, int id) + { + _gamepadDriver?.SetAccelerometerData(accel, id); + } + + public static void SetGryoData(Vector3 gyro, int id) + { + _gamepadDriver?.SetGryoData(gyro, id); + } + public static void SetStickAxis(StickInputId stick, Vector2 axes, int deviceId) { _gamepadDriver?.SetStickAxis(stick, axes, deviceId); @@ -460,6 +470,22 @@ namespace LibRyujinx gamePad.ButtonInputs[(int)button] = false; } } + + public void SetAccelerometerData(Vector3 accel, int deviceId) + { + if (_gamePads.TryGetValue(deviceId, out var gamePad)) + { + gamePad.Accelerometer = accel; + } + } + + public void SetGryoData(Vector3 gyro, int deviceId) + { + if (_gamePads.TryGetValue(deviceId, out var gamePad)) + { + gamePad.Gyro = gyro; + } + } } public class VirtualGamepad : IGamepad @@ -481,7 +507,7 @@ namespace LibRyujinx public void Dispose() { } - public GamepadFeaturesFlag Features { get; } + public GamepadFeaturesFlag Features { get; } = GamepadFeaturesFlag.Motion; public string Id { get; } internal readonly int IdInt; @@ -490,6 +516,8 @@ namespace LibRyujinx public bool IsConnected { get; } public Vector2[] StickInputs { get => _stickInputs; set => _stickInputs = value; } public bool[] ButtonInputs { get => _buttonInputs; set => _buttonInputs = value; } + public Vector3 Accelerometer { get; internal set; } + public Vector3 Gyro { get; internal set; } public bool IsPressed(GamepadButtonInputId inputId) { @@ -505,9 +533,18 @@ namespace LibRyujinx public Vector3 GetMotionData(MotionInputId inputId) { + if (inputId == MotionInputId.Accelerometer) + return Accelerometer; + else if (inputId == MotionInputId.Gyroscope) + return RadToDegree(Gyro); return new Vector3(); } + private static Vector3 RadToDegree(Vector3 rad) + { + return rad * (180 / MathF.PI); + } + public void SetTriggerThreshold(float triggerThreshold) { //throw new System.NotImplementedException(); diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/GameHost.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/GameHost.kt index 48cfb4ff6..81fef9c7b 100644 --- a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/GameHost.kt +++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/GameHost.kt @@ -96,7 +96,8 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su mainViewModel.controller?.connect() } - mainViewModel.physicalControllerManager?.connect() + val id = mainViewModel.physicalControllerManager?.connect() + mainViewModel.motionSensorManager?.setControllerId(id ?: -1) _nativeRyujinx.graphicsRendererSetSize( surfaceHolder.surfaceFrame.width(), diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/MainActivity.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/MainActivity.kt index f8e28f3e5..b00cf8ed1 100644 --- a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/MainActivity.kt +++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/MainActivity.kt @@ -28,6 +28,7 @@ import kotlin.math.abs class MainActivity : BaseActivity() { private var physicalControllerManager: PhysicalControllerManager = PhysicalControllerManager(this) + private lateinit var motionSensorManager: MotionSensorManager private var _isInit: Boolean = false var isGameRunning = false var storageHelper: SimpleStorageHelper? = null @@ -80,6 +81,8 @@ class MainActivity : BaseActivity() { } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + + motionSensorManager = MotionSensorManager(this) Thread.setDefaultUncaughtExceptionHandler(crashHandler) if( @@ -97,6 +100,7 @@ class MainActivity : BaseActivity() { mainViewModel = MainViewModel(this) mainViewModel!!.physicalControllerManager = physicalControllerManager + mainViewModel!!.motionSensorManager = motionSensorManager mainViewModel?.apply { setContent { @@ -194,6 +198,7 @@ class MainActivity : BaseActivity() { setFullScreen(true) NativeHelpers.instance.setTurboMode(true) force60HzRefreshRate(true) + motionSensorManager.register() } } @@ -204,5 +209,7 @@ class MainActivity : BaseActivity() { NativeHelpers.instance.setTurboMode(false) force60HzRefreshRate(false) } + + motionSensorManager.unregister() } } diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/MotionSensorManager.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/MotionSensorManager.kt new file mode 100644 index 000000000..f8a4e8c92 --- /dev/null +++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/MotionSensorManager.kt @@ -0,0 +1,111 @@ +package org.ryujinx.android + +import android.app.Activity +import android.hardware.Sensor +import android.hardware.SensorEvent +import android.hardware.SensorEventListener2 +import android.hardware.SensorManager +import android.view.OrientationEventListener + +class MotionSensorManager(val activity: MainActivity) : SensorEventListener2 { + private var isRegistered: Boolean = false + private var gyro: Sensor? + private var accelerometer: Sensor? + private var sensorManager: SensorManager = + activity.getSystemService(Activity.SENSOR_SERVICE) as SensorManager + private var controllerId: Int = -1 + + private val motionGyroOrientation : FloatArray = FloatArray(3) + private val motionAcelOrientation : FloatArray = FloatArray(3) + init { + accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER) + gyro = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE) + setOrientation90() + var orientationListener = object : OrientationEventListener(activity){ + override fun onOrientationChanged(orientation: Int) { + when{ + isWithinOrientationRange(orientation, 270) -> { + setOrientation270() + } + isWithinOrientationRange(orientation, 90) -> { + setOrientation90() + } + } + } + + private fun isWithinOrientationRange( + currentOrientation : Int, targetOrientation : Int, epsilon : Int = 90 + ) : Boolean { + return currentOrientation > targetOrientation - epsilon + && currentOrientation < targetOrientation + epsilon + } + } + } + + fun setOrientation270() { + motionGyroOrientation[0] = -1.0f + motionGyroOrientation[1] = 1.0f + motionGyroOrientation[2] = 1.0f + motionAcelOrientation[0] = 1.0f + motionAcelOrientation[1] = -1.0f + motionAcelOrientation[2] = -1.0f + } + fun setOrientation90() { + motionGyroOrientation[0] = 1.0f + motionGyroOrientation[1] = -1.0f + motionGyroOrientation[2] = 1.0f + motionAcelOrientation[0] = -1.0f + motionAcelOrientation[1] = 1.0f + motionAcelOrientation[2] = -1.0f + } + + fun setControllerId(id: Int){ + controllerId = id + } + + fun register(){ + if(isRegistered) + return + gyro?.apply { + sensorManager.registerListener(this@MotionSensorManager, gyro, SensorManager.SENSOR_DELAY_GAME) + } + accelerometer?.apply { + sensorManager.registerListener(this@MotionSensorManager, accelerometer, SensorManager.SENSOR_DELAY_GAME) + } + + isRegistered = true; + } + + fun unregister(){ + sensorManager.unregisterListener(this) + isRegistered = false + } + + override fun onSensorChanged(event: SensorEvent?) { + if (controllerId != -1) + event?.apply { + when (sensor.type) { + Sensor.TYPE_ACCELEROMETER -> { + val x = motionAcelOrientation[0] * event.values[1] + val y = motionAcelOrientation[1] * event.values[0] + val z = motionAcelOrientation[2] * event.values[2] + + RyujinxNative.instance.inputSetAccelerometerData(x, y, z, controllerId) + } + + Sensor.TYPE_GYROSCOPE -> { + val x = motionGyroOrientation[0] * event.values[1] + val y = motionGyroOrientation[1] * event.values[0] + val z = motionGyroOrientation[2] * event.values[2] + RyujinxNative.instance.inputSetGyroData(x, y, z, controllerId) + } + } + } + } + + override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) { + } + + override fun onFlushCompleted(sensor: Sensor?) { + } +} diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/PhysicalControllerManager.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/PhysicalControllerManager.kt index c61ac456e..df1a726dd 100644 --- a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/PhysicalControllerManager.kt +++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/PhysicalControllerManager.kt @@ -41,8 +41,13 @@ class PhysicalControllerManager(val activity: MainActivity) { } } - fun connect(){ + fun connect() : Int { controllerId = ryujinxNative.inputConnectGamepad(0) + return controllerId + } + + fun disconnect(){ + controllerId = -1 } private fun getGamePadButtonInputId(keycode: Int): GamePadButtonInputId { diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/RyujinxNative.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/RyujinxNative.kt index 5836372f4..d09362e11 100644 --- a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/RyujinxNative.kt +++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/RyujinxNative.kt @@ -49,6 +49,8 @@ class RyujinxNative { external fun inputSetButtonReleased(button: Int, id: Int) external fun inputConnectGamepad(index: Int): Int external fun inputSetStickAxis(stick: Int, x: Float, y: Float, id: Int) + external fun inputSetAccelerometerData(x: Float, y: Float, z: Float, id: Int) + external fun inputSetGyroData(x: Float, y: Float, z: Float, id: Int) external fun graphicsSetSurface(surface: Long, window: Long) external fun deviceCloseEmulation() external fun deviceSignalEmulationClose() diff --git a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/MainViewModel.kt b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/MainViewModel.kt index 6bf1fca72..7daad2393 100644 --- a/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/MainViewModel.kt +++ b/src/RyujinxAndroid/app/src/main/java/org/ryujinx/android/viewmodels/MainViewModel.kt @@ -14,6 +14,7 @@ import org.ryujinx.android.GameHost import org.ryujinx.android.GraphicsConfiguration import org.ryujinx.android.Logging import org.ryujinx.android.MainActivity +import org.ryujinx.android.MotionSensorManager import org.ryujinx.android.NativeGraphicsInterop import org.ryujinx.android.NativeHelpers import org.ryujinx.android.PerformanceManager @@ -26,6 +27,7 @@ import java.io.File @SuppressLint("WrongConstant") class MainViewModel(val activity: MainActivity) { var physicalControllerManager: PhysicalControllerManager? = null + var motionSensorManager: MotionSensorManager? = null var gameModel: GameModel? = null var controller: GameController? = null var performanceManager: PerformanceManager? = null @@ -62,6 +64,9 @@ class MainViewModel(val activity: MainActivity) { RyujinxNative.instance.deviceSignalEmulationClose() gameHost?.close() RyujinxNative.instance.deviceCloseEmulation() + motionSensorManager?.unregister() + physicalControllerManager?.disconnect() + motionSensorManager?.setControllerId(-1) } fun loadGame(game:GameModel) : Boolean { @@ -354,6 +359,7 @@ class MainViewModel(val activity: MainActivity) { activity.setFullScreen(true) navController?.navigate("game") activity.isGameRunning = true + motionSensorManager?.register() } fun setProgressStates(