mirror of
https://github.com/SSimco/Cemu.git
synced 2024-11-23 13:29:38 +00:00
Added support for motion
This commit is contained in:
parent
d0eac6f470
commit
81c0e4a465
@ -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"
|
||||
|
@ -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;
|
||||
}
|
||||
};
|
@ -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);
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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) {
|
||||
}
|
||||
}
|
@ -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) {
|
||||
|
@ -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"
|
||||
|
@ -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>
|
@ -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;
|
||||
}
|
@ -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();
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user