Added support for motion

This commit is contained in:
SSimco 2024-09-02 14:24:13 +03:00
parent d0eac6f470
commit 81c0e4a465
13 changed files with 177 additions and 8 deletions

View File

@ -20,6 +20,7 @@
tools:targetApi="31">
<activity
android:name=".emulation.EmulationActivity"
android:configChanges="orientation|screenSize"
android:launchMode="singleTop"
android:parentActivityName=".MainActivity"
android:screenOrientation="userLandscape"

View File

@ -44,6 +44,8 @@ class EmulationState
if (status.has_value())
touchInfo.left_down = touchInfo.left_down_toggle = status.value();
}
WiiUMotionHandler m_wiiUMotionHandler{};
long m_lastMotionTimestamp;
public:
void initializeEmulation()
@ -53,6 +55,7 @@ class EmulationState
FilesystemAndroid::setFilesystemCallbacks(std::make_shared<AndroidFilesystemCallbacks>());
NetworkConfig::LoadOnce();
InputManager::instance().load();
auto& instance = InputManager::instance();
InitializeGlobalVulkan();
createCemuDirectories();
LatteOverlay_init();
@ -337,4 +340,19 @@ class EmulationState
{
onTouchEvent(x, y, isTV, true);
}
void onMotion(long timestamp, float gyroX, float gyroY, float gyroZ, float accelX, float accelY, float accelZ)
{
float deltaTime = (timestamp - m_lastMotionTimestamp) * 1e-9f;
m_wiiUMotionHandler.processMotionSample(deltaTime, gyroX, gyroY, gyroZ, accelX * 0.098066f, -accelY * 0.098066f, -accelZ * 0.098066f);
m_lastMotionTimestamp = timestamp;
auto& deviceMotion = InputManager::instance().m_device_motion;
std::scoped_lock lock{deviceMotion.m_mutex};
deviceMotion.m_motion_sample = m_wiiUMotionHandler.getMotionSample();
}
void setMotionEnabled(bool enabled)
{
auto& deviceMotion = InputManager::instance().m_device_motion;
std::scoped_lock lock{deviceMotion.m_mutex};
deviceMotion.m_device_motion_enabled = enabled;
}
};

View File

@ -597,4 +597,16 @@ extern "C" JNIEXPORT jobject JNICALL
Java_info_cemu_Cemu_NativeLibrary_getInstalledGamesTitleIds(JNIEnv* env, [[maybe_unused]] jclass clazz)
{
return JNIUtils::createJavaLongArrayList(env, CafeTitleList::GetAllTitleIds());
}
extern "C" JNIEXPORT void JNICALL
Java_info_cemu_Cemu_NativeLibrary_onMotion([[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz, jlong timestamp, jfloat gyroX, jfloat gyroY, jfloat gyroZ, jfloat accelX, jfloat accelY, jfloat accelZ)
{
s_emulationState.onMotion(timestamp, gyroX, gyroY, gyroZ, accelX, accelY, accelZ);
}
extern "C" JNIEXPORT void JNICALL
Java_info_cemu_Cemu_NativeLibrary_setMotionEnabled([[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz, jboolean motionEnabled)
{
s_emulationState.setMotionEnabled(motionEnabled);
}

View File

@ -395,4 +395,8 @@ public class NativeLibrary {
public static native void onTouchMove(int x, int y, boolean isTV);
public static native void onTouchUp(int x, int y, boolean isTV);
public static native void onMotion(long timestamp, float gyroX, float gyroY, float gyroZ, float accelX, float accelY, float accelZ);
public static native void setMotionEnabled(boolean motionEnabled);
}

View File

@ -1,6 +1,7 @@
package info.cemu.Cemu.emulation;
import android.annotation.SuppressLint;
import android.content.res.Configuration;
import android.graphics.SurfaceTexture;
import android.os.Bundle;
import android.view.LayoutInflater;
@ -23,12 +24,12 @@ import androidx.fragment.app.Fragment;
import info.cemu.Cemu.NativeLibrary;
import info.cemu.Cemu.R;
import info.cemu.Cemu.databinding.FragmentEmulationBinding;
import info.cemu.Cemu.input.SensorManager;
import info.cemu.Cemu.inputoverlay.InputOverlaySettingsProvider;
import info.cemu.Cemu.inputoverlay.InputOverlaySurfaceView;
@SuppressLint("ClickableViewAccessibility")
public class EmulationFragment extends Fragment implements PopupMenu.OnMenuItemClickListener {
static private class OnSurfaceTouchListener implements View.OnTouchListener {
int currentPointerId = -1;
final boolean isTV;
@ -99,8 +100,10 @@ public class EmulationFragment extends Fragment implements PopupMenu.OnMenuItemC
private Surface testSurface;
private Toast toast;
private FragmentEmulationBinding binding;
private boolean isMotionEnabled;
private PopupMenu settingsMenu;
private InputOverlaySurfaceView inputOverlaySurfaceView;
private SensorManager sensorManager;
public EmulationFragment(long gameTitleId) {
this.gameTitleId = gameTitleId;
@ -112,14 +115,39 @@ public class EmulationFragment extends Fragment implements PopupMenu.OnMenuItemC
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
var inputOverlaySettingsProvider = new InputOverlaySettingsProvider(requireContext());
if (sensorManager == null)
sensorManager = new SensorManager(requireContext());
sensorManager.setIsLandscape(getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE);
overlaySettings = inputOverlaySettingsProvider.getOverlaySettings();
testSurfaceTexture = new SurfaceTexture(0);
testSurface = new Surface(testSurfaceTexture);
}
@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (sensorManager != null)
sensorManager.setIsLandscape(newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE);
}
@Override
public void onPause() {
super.onPause();
sensorManager.pauseListening();
}
@Override
public void onResume() {
super.onResume();
if (isMotionEnabled)
sensorManager.startListening();
}
@Override
public void onDestroy() {
super.onDestroy();
if (sensorManager != null)
sensorManager.pauseListening();
if (testSurface != null) testSurface.release();
if (testSurfaceTexture != null) testSurfaceTexture.release();
}
@ -182,6 +210,14 @@ public class EmulationFragment extends Fragment implements PopupMenu.OnMenuItemC
inputOverlaySurfaceView.resetInputs();
return true;
}
if (itemId == R.id.enable_motion) {
isMotionEnabled = !item.isChecked();
if (isMotionEnabled)
sensorManager.startListening();
else
sensorManager.pauseListening();
item.setChecked(isMotionEnabled);
}
return false;
}

View File

@ -44,6 +44,8 @@ public class InputManager {
return (event.getSource() & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK && event.getAction() == MotionEvent.ACTION_MOVE;
}
private static final float MIN_ABS_AXIS_VALUE = 0.33f;
public boolean mapMotionEventToMappingId(int controllerIndex, int mappingId, MotionEvent event) {
if (isMotionEventFromJoystick(event)) {
InputDevice device = event.getDevice();
@ -63,7 +65,6 @@ public class InputManager {
maxAbsAxisValue = Math.abs(axisValue);
}
}
float MIN_ABS_AXIS_VALUE = 0.33f;
if (maxAbsAxisValue > MIN_ABS_AXIS_VALUE) {
NativeLibrary.setControllerMapping(device.getDescriptor(), device.getName(), controllerIndex, mappingId, maxAxis);
return true;

View File

@ -0,0 +1,77 @@
package info.cemu.Cemu.input;
import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import info.cemu.Cemu.NativeLibrary;
public class SensorManager implements SensorEventListener {
private final android.hardware.SensorManager sensorManager;
private final Sensor accelerometer;
private final Sensor gyroscope;
private final boolean hasMotionData;
private float gyroX, gyroY, gyroZ;
private float accelX, accelY, accelZ;
private boolean hasAccelData, hasGyroData;
private boolean isLandscape = true;
public SensorManager(Context context) {
sensorManager = (android.hardware.SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
gyroscope = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE);
hasMotionData = accelerometer != null && gyroscope != null;
}
public void startListening() {
if (!hasMotionData) {
return;
}
NativeLibrary.setMotionEnabled(true);
sensorManager.registerListener(this, gyroscope, android.hardware.SensorManager.SENSOR_DELAY_GAME);
sensorManager.registerListener(this, accelerometer, android.hardware.SensorManager.SENSOR_DELAY_GAME);
}
public void setIsLandscape(boolean isLandscape) {
this.isLandscape = isLandscape;
}
public void pauseListening() {
if (!hasMotionData) {
return;
}
NativeLibrary.setMotionEnabled(false);
sensorManager.unregisterListener(this);
}
@Override
public void onSensorChanged(SensorEvent event) {
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
accelX = event.values[0];
accelY = event.values[1];
accelZ = event.values[2];
hasAccelData = true;
}
if (event.sensor.getType() == Sensor.TYPE_GYROSCOPE) {
gyroX = event.values[0];
gyroY = event.values[1];
gyroZ = event.values[2];
hasGyroData = true;
}
if (!hasAccelData || !hasGyroData) {
return;
}
hasAccelData = false;
hasGyroData = false;
if (isLandscape) {
NativeLibrary.onMotion(event.timestamp, gyroY, gyroZ, gyroX, accelY, accelZ, accelX);
return;
}
NativeLibrary.onMotion(event.timestamp, gyroX, gyroY, gyroZ, accelX, accelY, accelZ);
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
}

View File

@ -37,7 +37,7 @@ public class InputOverlaySettingsProvider {
private final SharedPreferences sharedPreferences;
private static final int defaultAlpha = 64;
private static final int DEFAULT_ALPHA = 64;
public InputOverlaySettingsProvider(Context context) {
this.sharedPreferences = context.getSharedPreferences("settings", Context.MODE_PRIVATE);
@ -90,7 +90,7 @@ public class InputOverlaySettingsProvider {
}
private int getAlpha() {
return sharedPreferences.getInt("alpha", defaultAlpha);
return sharedPreferences.getInt("alpha", DEFAULT_ALPHA);
}
private Optional<Rect> getRectangle(Input input) {

View File

@ -1,5 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/enable_motion"
android:checkable="true"
android:title="@string/enable_motion" />
<item
android:id="@+id/replace_tv_with_pad"
android:checkable="true"

View File

@ -164,4 +164,5 @@
<string name="show_pad">Show pad</string>
<string name="replace_tv_with_pad">Replace TV with PAD</string>
<string name="installed_games_title">Installed games</string>
<string name="enable_motion">Enable motion</string>
</resources>

View File

@ -967,3 +967,9 @@ void InputManager::update_thread()
std::this_thread::yield();
}
}
MotionSample InputManager::get_device_motion_sample() const
{
std::shared_lock lock(m_device_motion.m_mutex);
return m_device_motion.m_motion_sample;
}

View File

@ -98,6 +98,14 @@ public:
std::optional<glm::ivec2> get_right_down_mouse_info(bool* is_pad);
std::atomic<float> m_mouse_wheel;
struct DeviceMotion
{
mutable std::shared_mutex m_mutex;
MotionSample m_motion_sample;
bool m_device_motion_enabled;
} m_device_motion{};
MotionSample get_device_motion_sample() const;
private:
void update_thread();

View File

@ -1,5 +1,6 @@
#include "input/emulated/VPADController.h"
#include "input/api/Controller.h"
#include <mutex>
#if HAS_SDL
#include "input/api/SDL/SDLController.h"
#endif // HAS_SDL
@ -237,10 +238,11 @@ void VPADController::update_touch(VPADStatus_t& status)
void VPADController::update_motion(VPADStatus_t& status)
{
if (has_motion())
auto& input_manager = InputManager::instance();
bool has_device_motion = input_manager.m_device_motion.m_device_motion_enabled;
if (has_motion() || has_device_motion)
{
auto motionSample = get_motion_data();
MotionSample motionSample = has_device_motion ? input_manager.get_device_motion_sample() : get_motion_data();
glm::vec3 acc;
motionSample.getVPADAccelerometer(&acc[0]);
//const auto& acc = motionSample.getVPADAccelerometer();
@ -281,7 +283,6 @@ void VPADController::update_motion(VPADStatus_t& status)
}
bool pad_view;
auto& input_manager = InputManager::instance();
if (const auto right_mouse = input_manager.get_right_down_mouse_info(&pad_view))
{
const Vector2<float> mousePos(right_mouse->x, right_mouse->y);