Skip to content

Commit

Permalink
Added (incomplete) motion controls emulation support.
Browse files Browse the repository at this point in the history
  • Loading branch information
Helios-vmg committed Nov 9, 2024
1 parent e096fc7 commit df8e050
Show file tree
Hide file tree
Showing 36 changed files with 186 additions and 70 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,10 @@ public class MotionConfigController
/// Enable Motion Controls
/// </summary>
public bool EnableMotion { get; set; }

/// <summary>
/// Enable translating sticks to motion controls.
/// </summary>
public bool SticksToMotion { get; set; }
}
}
3 changes: 3 additions & 0 deletions src/Ryujinx.Common/Configuration/Hid/KeyboardHotkeys.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using System.Collections.Generic;

namespace Ryujinx.Common.Configuration.Hid
{
public class KeyboardHotkeys
Expand All @@ -11,5 +13,6 @@ public class KeyboardHotkeys
public Key ResScaleDown { get; set; }
public Key VolumeUp { get; set; }
public Key VolumeDown { get; set; }
public Key ToggleMotionEmulation { get; set; }
}
}
4 changes: 2 additions & 2 deletions src/Ryujinx.Gtk3/Input/GTK3/GTK3Keyboard.cs
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ private static (short, short) GetStickValues(ref KeyboardStateSnapshot snapshot,
return ((short)(stick.X * short.MaxValue), (short)(stick.Y * short.MaxValue));
}

public GamepadStateSnapshot GetMappedStateSnapshot()
public GamepadStateSnapshot GetMappedStateSnapshot(bool _)
{
KeyboardStateSnapshot rawState = GetKeyboardStateSnapshot();
GamepadStateSnapshot result = default;
Expand Down Expand Up @@ -131,7 +131,7 @@ public GamepadStateSnapshot GetMappedStateSnapshot()
return result;
}

public GamepadStateSnapshot GetStateSnapshot()
public GamepadStateSnapshot GetStateSnapshot(bool _)
{
throw new NotSupportedException();
}
Expand Down
4 changes: 2 additions & 2 deletions src/Ryujinx.Gtk3/Input/GTK3/GTK3Mouse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public Vector2 GetScroll()
return _driver.Scroll;
}

public GamepadStateSnapshot GetMappedStateSnapshot()
public GamepadStateSnapshot GetMappedStateSnapshot(bool _)
{
throw new NotImplementedException();
}
Expand All @@ -46,7 +46,7 @@ public Vector3 GetMotionData(MotionInputId inputId)
throw new NotImplementedException();
}

public GamepadStateSnapshot GetStateSnapshot()
public GamepadStateSnapshot GetStateSnapshot(bool _)
{
throw new NotImplementedException();
}
Expand Down
4 changes: 2 additions & 2 deletions src/Ryujinx.Headless.SDL2/SDL2Mouse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public Vector2 GetScroll()
return _driver.Scroll;
}

public GamepadStateSnapshot GetMappedStateSnapshot()
public GamepadStateSnapshot GetMappedStateSnapshot(bool _)
{
throw new NotImplementedException();
}
Expand All @@ -47,7 +47,7 @@ public Vector3 GetMotionData(MotionInputId inputId)
throw new NotImplementedException();
}

public GamepadStateSnapshot GetStateSnapshot()
public GamepadStateSnapshot GetStateSnapshot(bool _)
{
throw new NotImplementedException();
}
Expand Down
8 changes: 4 additions & 4 deletions src/Ryujinx.Input.SDL2/SDL2Gamepad.cs
Original file line number Diff line number Diff line change
Expand Up @@ -265,14 +265,14 @@ public void SetConfiguration(InputConfig configuration)
}
}

public GamepadStateSnapshot GetStateSnapshot()
public GamepadStateSnapshot GetStateSnapshot(bool ignoreSticks)
{
return IGamepad.GetStateSnapshot(this);
return IGamepad.GetStateSnapshot(this, ignoreSticks);
}

public GamepadStateSnapshot GetMappedStateSnapshot()
public GamepadStateSnapshot GetMappedStateSnapshot(bool ignoreSticks)
{
GamepadStateSnapshot rawState = GetStateSnapshot();
GamepadStateSnapshot rawState = GetStateSnapshot(ignoreSticks);
GamepadStateSnapshot result = default;

lock (_userMappingLock)
Expand Down
4 changes: 2 additions & 2 deletions src/Ryujinx.Input.SDL2/SDL2Keyboard.cs
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@ private static (short, short) GetStickValues(ref KeyboardStateSnapshot snapshot,
return ((short)(stick.X * short.MaxValue), (short)(stick.Y * short.MaxValue));
}

public GamepadStateSnapshot GetMappedStateSnapshot()
public GamepadStateSnapshot GetMappedStateSnapshot(bool _)
{
KeyboardStateSnapshot rawState = GetKeyboardStateSnapshot();
GamepadStateSnapshot result = default;
Expand Down Expand Up @@ -335,7 +335,7 @@ public GamepadStateSnapshot GetMappedStateSnapshot()
return result;
}

public GamepadStateSnapshot GetStateSnapshot()
public GamepadStateSnapshot GetStateSnapshot(bool _)
{
throw new NotSupportedException();
}
Expand Down
4 changes: 2 additions & 2 deletions src/Ryujinx.Input/Assigner/GamepadButtonAssigner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public void Initialize()
{
if (_gamepad != null)
{
_currState = _gamepad.GetStateSnapshot();
_currState = _gamepad.GetStateSnapshot(false);
_prevState = _currState;
}
}
Expand All @@ -43,7 +43,7 @@ public void ReadInput()
if (_gamepad != null)
{
_prevState = _currState;
_currState = _gamepad.GetStateSnapshot();
_currState = _gamepad.GetStateSnapshot(false);
}

CollectButtonStats();
Expand Down
133 changes: 91 additions & 42 deletions src/Ryujinx.Input/HLE/NpadController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ public HLEKeyboardMappingEntry(Key targetKey, byte target)

private IGamepad _gamepad;
private InputConfig _config;
public StandardControllerInputConfig StandardControllerInputConfig => _config as StandardControllerInputConfig;

public IGamepadDriver GamepadDriver { get; private set; }
public GamepadStateSnapshot State { get; private set; }
Expand Down Expand Up @@ -273,61 +274,109 @@ private void UpdateMotionInput(MotionConfigController motionConfig)
}
}

public void Update()
private static double Transform(double x)
{
// _gamepad may be altered by other threads
var gamepad = _gamepad;
return (Math.Pow(30, Math.Abs(x)) - 1) / 20 * Math.Sign(x);
}

if (gamepad != null && GamepadDriver != null)
private void EmulateMotion(IGamepad gamepad, StandardControllerInputConfig config)
{
var (lx, ly) = gamepad.GetStick(StickInputId.Left);
var (rx, _) = gamepad.GetStick(StickInputId.Right);

var gyroscope = new Vector3(-ly, lx, -rx) * 100;
var x = Transform(lx);
var y = Transform(ly);
var accelerometer = new Vector3((float)x, (float)y, -1);

_leftMotionInput.Update(accelerometer, gyroscope, (ulong)PerformanceCounter.ElapsedNanoseconds / 1000, config.Motion.Sensitivity, (float)config.Motion.GyroDeadzone);
if (config.ControllerType == ConfigControllerType.JoyconPair)
_rightMotionInput = _leftMotionInput;
}

private void ReadGamepadMotion(StandardControllerInputConfig controllerConfig, IGamepad gamepad)
{
if (gamepad.Features.HasFlag(GamepadFeaturesFlag.Motion))
{
State = gamepad.GetMappedStateSnapshot();
Vector3 accelerometer = gamepad.GetMotionData(MotionInputId.Accelerometer);
Vector3 gyroscope = gamepad.GetMotionData(MotionInputId.Gyroscope);

accelerometer = new Vector3(accelerometer.X, -accelerometer.Z, accelerometer.Y);
gyroscope = new Vector3(gyroscope.X, -gyroscope.Z, gyroscope.Y);

_leftMotionInput.Update(accelerometer, gyroscope,
(ulong)PerformanceCounter.ElapsedNanoseconds / 1000,
controllerConfig.Motion.Sensitivity, (float)controllerConfig.Motion.GyroDeadzone);

if (_config is StandardControllerInputConfig controllerConfig && controllerConfig.Motion.EnableMotion)
if (controllerConfig.ControllerType == ConfigControllerType.JoyconPair)
{
if (controllerConfig.Motion.MotionBackend == MotionInputBackendType.GamepadDriver)
{
if (gamepad.Features.HasFlag(GamepadFeaturesFlag.Motion))
{
Vector3 accelerometer = gamepad.GetMotionData(MotionInputId.Accelerometer);
Vector3 gyroscope = gamepad.GetMotionData(MotionInputId.Gyroscope);
_rightMotionInput = _leftMotionInput;
}
}
}

private void ReadCemuMotion(StandardControllerInputConfig controllerConfig)
{
if (controllerConfig.Motion is CemuHookMotionConfigController cemuControllerConfig)
{
int clientId = (int)controllerConfig.PlayerIndex;

accelerometer = new Vector3(accelerometer.X, -accelerometer.Z, accelerometer.Y);
gyroscope = new Vector3(gyroscope.X, -gyroscope.Z, gyroscope.Y);
// First of all ensure we are registered
_cemuHookClient.RegisterClient(clientId, cemuControllerConfig.DsuServerHost,
cemuControllerConfig.DsuServerPort);

_leftMotionInput.Update(accelerometer, gyroscope, (ulong)PerformanceCounter.ElapsedNanoseconds / 1000, controllerConfig.Motion.Sensitivity, (float)controllerConfig.Motion.GyroDeadzone);
// Then request and retrieve the data
_cemuHookClient.RequestData(clientId, cemuControllerConfig.Slot);
_cemuHookClient.TryGetData(clientId, cemuControllerConfig.Slot, out _leftMotionInput);

if (controllerConfig.ControllerType == ConfigControllerType.JoyconPair)
{
_rightMotionInput = _leftMotionInput;
}
}
if (controllerConfig.ControllerType == ConfigControllerType.JoyconPair)
{
if (!cemuControllerConfig.MirrorInput)
{
_cemuHookClient.RequestData(clientId, cemuControllerConfig.AltSlot);
_cemuHookClient.TryGetData(clientId, cemuControllerConfig.AltSlot,
out _rightMotionInput);
}
else if (controllerConfig.Motion.MotionBackend == MotionInputBackendType.CemuHook && controllerConfig.Motion is CemuHookMotionConfigController cemuControllerConfig)
else
{
int clientId = (int)controllerConfig.PlayerIndex;

// First of all ensure we are registered
_cemuHookClient.RegisterClient(clientId, cemuControllerConfig.DsuServerHost, cemuControllerConfig.DsuServerPort);

// Then request and retrieve the data
_cemuHookClient.RequestData(clientId, cemuControllerConfig.Slot);
_cemuHookClient.TryGetData(clientId, cemuControllerConfig.Slot, out _leftMotionInput);

if (controllerConfig.ControllerType == ConfigControllerType.JoyconPair)
{
if (!cemuControllerConfig.MirrorInput)
{
_cemuHookClient.RequestData(clientId, cemuControllerConfig.AltSlot);
_cemuHookClient.TryGetData(clientId, cemuControllerConfig.AltSlot, out _rightMotionInput);
}
else
{
_rightMotionInput = _leftMotionInput;
}
}
_rightMotionInput = _leftMotionInput;
}
}
}
}

private void ReadMotion(IGamepad gamepad, StandardControllerInputConfig controllerConfig)
{
switch (controllerConfig.Motion.MotionBackend)
{
case MotionInputBackendType.GamepadDriver:
ReadGamepadMotion(controllerConfig, gamepad);
break;
case MotionInputBackendType.CemuHook:
ReadCemuMotion(controllerConfig);
break;
default:
break;
}
}

public void Update()
{
// _gamepad may be altered by other threads
var gamepad = _gamepad;

if (gamepad != null && GamepadDriver != null)
{
State = gamepad.GetMappedStateSnapshot(StandardControllerInputConfig?.Motion.SticksToMotion ?? false);

if (_config is StandardControllerInputConfig controllerConfig)
{
if (controllerConfig.Motion.SticksToMotion)
EmulateMotion(gamepad, controllerConfig);
else if (controllerConfig.Motion.EnableMotion)
ReadMotion(gamepad, controllerConfig);
}
}
else
{
// Reset states
Expand Down
2 changes: 2 additions & 0 deletions src/Ryujinx.Input/HLE/NpadManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ public NpadManager(IGamepadDriver keyboardDriver, IGamepadDriver gamepadDriver,
_gamepadDriver.OnGamepadDisconnected += HandleOnGamepadDisconnected;
}

public IEnumerable<NpadController> Controllers => _controllers;

private void RefreshInputConfigForHLE()
{
lock (_lock)
Expand Down
22 changes: 13 additions & 9 deletions src/Ryujinx.Input/IGamepad.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,35 +77,39 @@ public interface IGamepad : IDisposable
/// Get a snaphost of the state of the gamepad that is remapped with the informations from the <see cref="InputConfig"/> set via <see cref="SetConfiguration(InputConfig)"/>.
/// </summary>
/// <returns>A remapped snaphost of the state of the gamepad.</returns>
GamepadStateSnapshot GetMappedStateSnapshot();
GamepadStateSnapshot GetMappedStateSnapshot(bool ignoreSticks);

/// <summary>
/// Get a snaphost of the state of the gamepad.
/// </summary>
/// <returns>A snaphost of the state of the gamepad.</returns>
GamepadStateSnapshot GetStateSnapshot();
GamepadStateSnapshot GetStateSnapshot(bool ignoreSticks);

/// <summary>
/// Get a snaphost of the state of a gamepad.
/// </summary>
/// <param name="gamepad">The gamepad to do a snapshot of</param>
/// <param name="ignoreSticks">Forces the stick states to zero</param>
/// <returns>A snaphost of the state of the gamepad.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static GamepadStateSnapshot GetStateSnapshot(IGamepad gamepad)
static GamepadStateSnapshot GetStateSnapshot(IGamepad gamepad, bool ignoreSticks)
{
// NOTE: Update Array size if JoystickInputId is changed.
Array3<Array2<float>> joysticksState = default;

for (StickInputId inputId = StickInputId.Left; inputId < StickInputId.Count; inputId++)
if (!ignoreSticks)
{
(float state0, float state1) = gamepad.GetStick(inputId);
for (StickInputId inputId = StickInputId.Left; inputId < StickInputId.Count; inputId++)
{
(float state0, float state1) = gamepad.GetStick(inputId);

Array2<float> state = default;
Array2<float> state = default;

state[0] = state0;
state[1] = state1;
state[0] = state0;
state[1] = state1;

joysticksState[(int)inputId] = state;
joysticksState[(int)inputId] = state;
}
}

// NOTE: Update Array size if GamepadInputId is changed.
Expand Down
12 changes: 12 additions & 0 deletions src/Ryujinx/AppHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1200,6 +1200,14 @@ private bool UpdateFrame()

_viewModel.Volume = Device.GetVolume();
break;
case KeyboardHotkeyState.ToggleMotionEmulation:
foreach (var controller in NpadManager.Controllers)
{
var motion = controller?.StandardControllerInputConfig?.Motion;
if (motion != null)
motion.SticksToMotion = !motion.SticksToMotion;
}
break;
case KeyboardHotkeyState.None:
(_keyboardInterface as AvaloniaKeyboard).Clear();
break;
Expand Down Expand Up @@ -1273,6 +1281,10 @@ private KeyboardHotkeyState GetHotkeyState()
{
state = KeyboardHotkeyState.VolumeDown;
}
else if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ToggleMotionEmulation))
{
state = KeyboardHotkeyState.ToggleMotionEmulation;
}

return state;
}
Expand Down
1 change: 1 addition & 0 deletions src/Ryujinx/Assets/Locales/ar_SA.json
Original file line number Diff line number Diff line change
Expand Up @@ -734,6 +734,7 @@
"RyujinxUpdaterMessage": "هل تريد تحديث ريوجينكس إلى أحدث إصدار؟",
"SettingsTabHotkeysVolumeUpHotkey": "زيادة مستوى الصوت:",
"SettingsTabHotkeysVolumeDownHotkey": "خفض مستوى الصوت:",
"SettingsTabHotkeysToggleMotionEmulationHotkey": "Toggle Motion Emulation:",
"SettingsEnableMacroHLE": "تمكين Maro HLE",
"SettingsEnableMacroHLETooltip": "محاكاة عالية المستوى لكود مايكرو وحدة معالجة الرسوميات.\n\nيعمل على تحسين الأداء، ولكنه قد يسبب خللا رسوميا في بعض الألعاب.\n\nاتركه مفعلا إذا لم تكن متأكدا.",
"SettingsEnableColorSpacePassthrough": "عبور مساحة اللون",
Expand Down
1 change: 1 addition & 0 deletions src/Ryujinx/Assets/Locales/de_DE.json
Original file line number Diff line number Diff line change
Expand Up @@ -734,6 +734,7 @@
"RyujinxUpdaterMessage": "Möchtest du Ryujinx auf die neueste Version aktualisieren?",
"SettingsTabHotkeysVolumeUpHotkey": "Lautstärke erhöhen:",
"SettingsTabHotkeysVolumeDownHotkey": "Lautstärke verringern:",
"SettingsTabHotkeysToggleMotionEmulationHotkey": "Toggle Motion Emulation:",
"SettingsEnableMacroHLE": "HLE Makros aktivieren",
"SettingsEnableMacroHLETooltip": "High-Level-Emulation von GPU-Makrocode.\n\nVerbessert die Leistung, kann aber in einigen Spielen zu Grafikfehlern führen.\n\nBei Unsicherheit AKTIVIEREN.",
"SettingsEnableColorSpacePassthrough": "Farbraum Passthrough",
Expand Down
Loading

0 comments on commit df8e050

Please sign in to comment.