mirror of
https://github.com/RPCS3/rpcs3.git
synced 2025-03-14 10:21:21 +00:00
fake move: add gyro support
This commit is contained in:
parent
92d0707291
commit
415c2d0795
@ -1142,7 +1142,7 @@ static inline void pos_to_gem_state(u32 gem_num, gem_config::gem_controller& con
|
||||
gem_state->handle_pos[3] = 0.f;
|
||||
|
||||
// Calculate orientation
|
||||
if (g_cfg.io.move == move_handler::real)
|
||||
if (g_cfg.io.move == move_handler::real || (g_cfg.io.move == move_handler::fake && move_data.orientation_enabled))
|
||||
{
|
||||
gem_state->quat[0] = move_data.quaternion[0]; // x
|
||||
gem_state->quat[1] = move_data.quaternion[1]; // y
|
||||
@ -1151,14 +1151,11 @@ static inline void pos_to_gem_state(u32 gem_num, gem_config::gem_controller& con
|
||||
}
|
||||
else
|
||||
{
|
||||
static constexpr f32 PI = 3.14159265f;
|
||||
const auto degree_to_rad = [](f32 degree) -> f32 { return degree * PI / 180.0f; };
|
||||
|
||||
const f32 max_angle_per_side_h = g_cfg.io.fake_move_rotation_cone_h / 2.0f;
|
||||
const f32 max_angle_per_side_v = g_cfg.io.fake_move_rotation_cone_v / 2.0f;
|
||||
const f32 roll = -degree_to_rad((image_y - half_height) / half_height * max_angle_per_side_v); // This is actually the pitch
|
||||
const f32 pitch = -degree_to_rad((image_x - half_width) / half_width * max_angle_per_side_h); // This is actually the yaw
|
||||
const f32 yaw = degree_to_rad(0.0f);
|
||||
const f32 roll = -PadHandlerBase::degree_to_rad((image_y - half_height) / half_height * max_angle_per_side_v); // This is actually the pitch
|
||||
const f32 pitch = -PadHandlerBase::degree_to_rad((image_x - half_width) / half_width * max_angle_per_side_h); // This is actually the yaw
|
||||
const f32 yaw = PadHandlerBase::degree_to_rad(0.0f);
|
||||
const f32 cr = std::cos(roll * 0.5f);
|
||||
const f32 sr = std::sin(roll * 0.5f);
|
||||
const f32 cp = std::cos(pitch * 0.5f);
|
||||
@ -1318,7 +1315,7 @@ static void ds3_pos_to_gem_state(u32 gem_num, gem_config::gem_controller& contro
|
||||
|
||||
if constexpr (std::is_same_v<T, vm::ptr<CellGemState>>)
|
||||
{
|
||||
pos_to_gem_state(gem_num, controller, gem_state, ds3_pos_x, ds3_pos_y, ds3_max_x, ds3_max_y, {});
|
||||
pos_to_gem_state(gem_num, controller, gem_state, ds3_pos_x, ds3_pos_y, ds3_max_x, ds3_max_y, pad->move_data);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, vm::ptr<CellGemImageState>>)
|
||||
{
|
||||
@ -2147,6 +2144,7 @@ error_code cellGemGetInertialState(u32 gem_num, u32 state_flag, u64 timestamp, v
|
||||
switch (g_cfg.io.move)
|
||||
{
|
||||
case move_handler::real:
|
||||
case move_handler::fake:
|
||||
{
|
||||
// Get temperature and sensor data
|
||||
{
|
||||
@ -2155,7 +2153,7 @@ error_code cellGemGetInertialState(u32 gem_num, u32 state_flag, u64 timestamp, v
|
||||
const auto handler = pad::get_current_handler();
|
||||
const auto& pad = ::at32(handler->GetPads(), pad_num(gem_num));
|
||||
|
||||
if (pad && pad->m_pad_handler == pad_handler::move && (pad->m_port_status & CELL_PAD_STATUS_CONNECTED))
|
||||
if (pad && (pad->m_port_status & CELL_PAD_STATUS_CONNECTED))
|
||||
{
|
||||
inertial_state->temperature = pad->move_data.temperature;
|
||||
inertial_state->accelerometer[0] = pad->move_data.accelerometer_x;
|
||||
@ -2170,9 +2168,6 @@ error_code cellGemGetInertialState(u32 gem_num, u32 state_flag, u64 timestamp, v
|
||||
ds3_input_to_pad(gem_num, inertial_state->pad.digitalbuttons, inertial_state->pad.analog_T);
|
||||
break;
|
||||
}
|
||||
case move_handler::fake:
|
||||
ds3_input_to_pad(gem_num, inertial_state->pad.digitalbuttons, inertial_state->pad.analog_T);
|
||||
break;
|
||||
case move_handler::mouse:
|
||||
case move_handler::raw_mouse:
|
||||
mouse_input_to_pad(gem_num, inertial_state->pad.digitalbuttons, inertial_state->pad.analog_T);
|
||||
|
@ -48,6 +48,7 @@ public:
|
||||
|
||||
cfg->pressure_intensity_button.def = "";
|
||||
cfg->analog_limiter_button.def = "";
|
||||
cfg->orientation_reset_button.def = "";
|
||||
|
||||
// Apply defaults
|
||||
cfg->from_default();
|
||||
|
@ -2,6 +2,7 @@
|
||||
#include "PadHandler.h"
|
||||
#include "Emu/system_utils.hpp"
|
||||
#include "Emu/system_config.h"
|
||||
#include "Emu/Cell/timers.hpp"
|
||||
#include "Input/pad_thread.h"
|
||||
#include "Input/product_info.h"
|
||||
|
||||
@ -494,6 +495,12 @@ bool PadHandlerBase::bindPadToDevice(std::shared_ptr<Pad> pad)
|
||||
pad->m_analog_limiter_button_index = static_cast<s32>(pad->m_buttons.size()) - 1;
|
||||
}
|
||||
|
||||
if (b_has_orientation)
|
||||
{
|
||||
pad->m_buttons.emplace_back(special_button_offset, mapping[button::orientation_reset_button], special_button_value::orientation_reset);
|
||||
pad->m_orientation_reset_button_index = static_cast<s32>(pad->m_buttons.size()) - 1;
|
||||
}
|
||||
|
||||
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, mapping[button::up], CELL_PAD_CTRL_UP);
|
||||
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, mapping[button::down], CELL_PAD_CTRL_DOWN);
|
||||
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, mapping[button::left], CELL_PAD_CTRL_LEFT);
|
||||
@ -600,6 +607,11 @@ std::array<std::set<u32>, PadHandlerBase::button::button_count> PadHandlerBase::
|
||||
mapping[button::analog_limiter_button] = FindKeyCodes<u32, u32>(button_list, cfg->analog_limiter_button);
|
||||
}
|
||||
|
||||
if (b_has_orientation)
|
||||
{
|
||||
mapping[button::orientation_reset_button] = FindKeyCodes<u32, u32>(button_list, cfg->orientation_reset_button);
|
||||
}
|
||||
|
||||
return mapping;
|
||||
}
|
||||
|
||||
@ -739,6 +751,8 @@ void PadHandlerBase::process()
|
||||
if (!device || !pad)
|
||||
continue;
|
||||
|
||||
pad->move_data.orientation_enabled = b_has_orientation && device->config && device->config->orientation_enabled.get();
|
||||
|
||||
const connection status = update_connection(device);
|
||||
|
||||
switch (status)
|
||||
@ -754,6 +768,11 @@ void PadHandlerBase::process()
|
||||
|
||||
last_connection_status[i] = true;
|
||||
connected_devices++;
|
||||
|
||||
if (b_has_orientation)
|
||||
{
|
||||
device->reset_orientation();
|
||||
}
|
||||
}
|
||||
|
||||
if (status == connection::no_data)
|
||||
@ -790,6 +809,11 @@ void PadHandlerBase::process()
|
||||
|
||||
last_connection_status[i] = false;
|
||||
connected_devices--;
|
||||
|
||||
if (b_has_orientation)
|
||||
{
|
||||
device->reset_orientation();
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@ -797,6 +821,142 @@ void PadHandlerBase::process()
|
||||
|
||||
get_mapping(m_bindings[i]);
|
||||
get_extended_info(m_bindings[i]);
|
||||
get_orientation(m_bindings[i]);
|
||||
apply_pad_data(m_bindings[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void PadHandlerBase::set_raw_orientation(ps_move_data& move_data, f32 accel_x, f32 accel_y, f32 accel_z, f32 gyro_x, f32 gyro_y, f32 gyro_z)
|
||||
{
|
||||
if (!move_data.orientation_enabled)
|
||||
{
|
||||
move_data.reset_sensors();
|
||||
return;
|
||||
}
|
||||
|
||||
// This function expects DS3 sensor accel values in linear velocity (m/s²) and gyro values in angular velocity (degree/s)
|
||||
// The default position is flat on the ground, pointing forward.
|
||||
// The accelerometers constantly measure G forces.
|
||||
// The gyros measure changes in orientation and will reset when the device isn't moved anymore.
|
||||
move_data.accelerometer_x = -accel_x; // move_data: Increases if the device is rolled to the left
|
||||
move_data.accelerometer_y = accel_z; // move_data: Increases if the device is pitched upwards
|
||||
move_data.accelerometer_z = accel_y; // move_data: Increases if the device is moved upwards
|
||||
move_data.gyro_x = degree_to_rad(-gyro_x); // move_data: Increases if the device is pitched upwards
|
||||
move_data.gyro_y = degree_to_rad(gyro_z); // move_data: Increases if the device is rolled to the right
|
||||
move_data.gyro_z = degree_to_rad(-gyro_y); // move_data: Increases if the device is yawed to the left
|
||||
}
|
||||
|
||||
void PadHandlerBase::set_raw_orientation(Pad& pad)
|
||||
{
|
||||
if (!pad.move_data.orientation_enabled)
|
||||
{
|
||||
pad.move_data.reset_sensors();
|
||||
return;
|
||||
}
|
||||
|
||||
// acceleration (linear velocity in m/s²)
|
||||
const f32 accel_x = (pad.m_sensors[0].m_value - 512) / static_cast<f32>(MOTION_ONE_G);
|
||||
const f32 accel_y = (pad.m_sensors[1].m_value - 512) / static_cast<f32>(MOTION_ONE_G);
|
||||
const f32 accel_z = (pad.m_sensors[2].m_value - 512) / static_cast<f32>(MOTION_ONE_G);
|
||||
|
||||
// gyro (angular velocity in degree/s)
|
||||
constexpr f32 gyro_x = 0.0f;
|
||||
const f32 gyro_y = (pad.m_sensors[3].m_value - 512) / (123.f / 90.f);
|
||||
constexpr f32 gyro_z = 0.0f;
|
||||
|
||||
set_raw_orientation(pad.move_data, accel_x, accel_y, accel_z, gyro_x, gyro_y, gyro_z);
|
||||
}
|
||||
|
||||
void PadHandlerBase::get_orientation(const pad_ensemble& binding) const
|
||||
{
|
||||
if (!b_has_orientation) return;
|
||||
|
||||
const auto& pad = binding.pad;
|
||||
const auto& device = binding.device;
|
||||
if (!pad || !device) return;
|
||||
|
||||
if (pad->move_data.calibration_requested)
|
||||
{
|
||||
device->reset_orientation();
|
||||
pad->move_data.quaternion = ps_move_data::default_quaternion;
|
||||
pad->move_data.calibration_succeeded = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!pad->move_data.orientation_enabled || pad->get_orientation_reset_button_active(pad->m_player_id))
|
||||
{
|
||||
// This can be called extensively in quick succession, so let's just reset the pointer instead of creating a new object.
|
||||
device->ahrs.reset();
|
||||
pad->move_data.quaternion = ps_move_data::default_quaternion;
|
||||
return;
|
||||
}
|
||||
|
||||
device->update_orientation(pad->move_data);
|
||||
}
|
||||
|
||||
void PadDevice::reset_orientation()
|
||||
{
|
||||
// Initialize Fusion
|
||||
ahrs = std::make_shared<FusionAhrs>();
|
||||
FusionAhrsInitialise(ahrs.get());
|
||||
ahrs->settings.convention = FusionConvention::FusionConventionEnu;
|
||||
ahrs->settings.gain = 0.0f; // If gain is set, the algorithm tries to adjust the orientation over time.
|
||||
FusionAhrsSetSettings(ahrs.get(), &ahrs->settings);
|
||||
FusionAhrsReset(ahrs.get());
|
||||
}
|
||||
|
||||
void PadDevice::update_orientation(ps_move_data& move_data)
|
||||
{
|
||||
if (!ahrs)
|
||||
{
|
||||
reset_orientation();
|
||||
}
|
||||
|
||||
// Get elapsed time since last update
|
||||
const u64 now_us = get_system_time();
|
||||
const float elapsed_sec = (last_ahrs_update_time_us == 0) ? 0.0f : ((now_us - last_ahrs_update_time_us) / 1'000'000.0f);
|
||||
last_ahrs_update_time_us = now_us;
|
||||
|
||||
// The ps move handler's axis may differ from the Fusion axis, so we have to map them correctly.
|
||||
// Don't ask how the axis work. It's basically been trial and error.
|
||||
ensure(ahrs->settings.convention == FusionConvention::FusionConventionEnu); // East-North-Up
|
||||
|
||||
const FusionVector accelerometer{
|
||||
.axis {
|
||||
.x = -move_data.accelerometer_x,
|
||||
.y = +move_data.accelerometer_y,
|
||||
.z = +move_data.accelerometer_z
|
||||
}
|
||||
};
|
||||
|
||||
const FusionVector gyroscope{
|
||||
.axis {
|
||||
.x = +PadHandlerBase::rad_to_degree(move_data.gyro_x),
|
||||
.y = +PadHandlerBase::rad_to_degree(move_data.gyro_z),
|
||||
.z = -PadHandlerBase::rad_to_degree(move_data.gyro_y)
|
||||
}
|
||||
};
|
||||
|
||||
FusionVector magnetometer {};
|
||||
|
||||
if (move_data.magnetometer_enabled)
|
||||
{
|
||||
magnetometer = FusionVector{
|
||||
.axis {
|
||||
.x = move_data.magnetometer_x,
|
||||
.y = move_data.magnetometer_y,
|
||||
.z = move_data.magnetometer_z
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Update Fusion
|
||||
FusionAhrsUpdate(ahrs.get(), gyroscope, accelerometer, magnetometer, elapsed_sec);
|
||||
|
||||
// Get quaternion
|
||||
const FusionQuaternion quaternion = FusionAhrsGetQuaternion(ahrs.get());
|
||||
move_data.quaternion[0] = quaternion.array[1];
|
||||
move_data.quaternion[1] = quaternion.array[2];
|
||||
move_data.quaternion[2] = quaternion.array[3];
|
||||
move_data.quaternion[3] = quaternion.array[0];
|
||||
}
|
||||
|
@ -5,6 +5,15 @@
|
||||
#include "pad_config_types.h"
|
||||
#include "util/types.hpp"
|
||||
|
||||
#ifndef _MSC_VER
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wold-style-cast"
|
||||
#endif
|
||||
#include "3rdparty/fusion/fusion/Fusion/Fusion.h"
|
||||
#ifndef _MSC_VER
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
#include <cmath>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
@ -38,6 +47,12 @@ public:
|
||||
};
|
||||
color color_override{};
|
||||
bool color_override_active{};
|
||||
|
||||
std::shared_ptr<FusionAhrs> ahrs; // Used to calculate quaternions from sensor data
|
||||
u64 last_ahrs_update_time_us = 0; // Last ahrs update
|
||||
|
||||
void update_orientation(ps_move_data& move_data);
|
||||
void reset_orientation();
|
||||
};
|
||||
|
||||
struct pad_ensemble
|
||||
@ -125,6 +140,7 @@ protected:
|
||||
|
||||
pressure_intensity_button,
|
||||
analog_limiter_button,
|
||||
orientation_reset_button,
|
||||
|
||||
button_count
|
||||
};
|
||||
@ -153,6 +169,7 @@ protected:
|
||||
bool b_has_config = false;
|
||||
bool b_has_pressure_intensity_button = true;
|
||||
bool b_has_analog_limiter_button = true;
|
||||
bool b_has_orientation = false;
|
||||
|
||||
std::array<cfg_pad, MAX_GAMEPADS> m_pad_configs;
|
||||
std::vector<pad_ensemble> m_bindings;
|
||||
@ -301,6 +318,7 @@ public:
|
||||
bool has_battery_led() const { return b_has_battery_led; }
|
||||
bool has_pressure_intensity_button() const { return b_has_pressure_intensity_button; }
|
||||
bool has_analog_limiter_button() const { return b_has_analog_limiter_button; }
|
||||
bool has_orientation() const { return b_has_orientation; }
|
||||
|
||||
u16 NormalizeStickInput(u16 raw_value, s32 threshold, s32 multiplier, bool ignore_threshold = false) const;
|
||||
void convert_stick_values(u16& x_out, u16& y_out, s32 x_in, s32 y_in, u32 deadzone, u32 anti_deadzone, u32 padsquircling) const;
|
||||
@ -323,6 +341,18 @@ public:
|
||||
virtual void get_motion_sensors(const std::string& pad_id, const motion_callback& callback, const motion_fail_callback& fail_callback, motion_preview_values preview_values, const std::array<AnalogSensor, 4>& sensors);
|
||||
virtual std::unordered_map<u32, std::string> get_motion_axis_list() const { return {}; }
|
||||
|
||||
static constexpr f32 PI = 3.14159265f;
|
||||
|
||||
static f32 degree_to_rad(f32 degree)
|
||||
{
|
||||
return degree * PI / 180.0f;
|
||||
}
|
||||
|
||||
static f32 rad_to_degree(f32 radians)
|
||||
{
|
||||
return radians * 180.0f / PI;
|
||||
};
|
||||
|
||||
private:
|
||||
virtual std::shared_ptr<PadDevice> get_device(const std::string& /*device*/) { return nullptr; }
|
||||
virtual bool get_is_left_trigger(const std::shared_ptr<PadDevice>& /*device*/, u64 /*keyCode*/) { return false; }
|
||||
@ -336,10 +366,15 @@ private:
|
||||
virtual std::unordered_map<u64, u16> get_button_values(const std::shared_ptr<PadDevice>& /*device*/) { return {}; }
|
||||
virtual pad_preview_values get_preview_values(const std::unordered_map<u64, u16>& /*data*/) { return {}; }
|
||||
|
||||
void get_orientation(const pad_ensemble& binding) const;
|
||||
|
||||
protected:
|
||||
virtual std::array<std::set<u32>, PadHandlerBase::button::button_count> get_mapped_key_codes(const std::shared_ptr<PadDevice>& device, const cfg_pad* cfg);
|
||||
virtual void get_mapping(const pad_ensemble& binding);
|
||||
void TranslateButtonPress(const std::shared_ptr<PadDevice>& device, u64 keyCode, bool& pressed, u16& val, bool use_stick_multipliers, bool ignore_stick_threshold = false, bool ignore_trigger_threshold = false);
|
||||
void init_configs();
|
||||
cfg_pad* get_config(const std::string& pad_id);
|
||||
|
||||
static void set_raw_orientation(ps_move_data& move_data, f32 accel_x, f32 accel_y, f32 accel_z, f32 gyro_x, f32 gyro_y, f32 gyro_z);
|
||||
static void set_raw_orientation(Pad& pad);
|
||||
};
|
||||
|
@ -66,6 +66,9 @@ struct cfg_pad final : cfg::node
|
||||
cfg_sensor motion_sensor_z{ this, "Motion Sensor Z" };
|
||||
cfg_sensor motion_sensor_g{ this, "Motion Sensor G" };
|
||||
|
||||
cfg::string orientation_reset_button{ this, "Orientation Reset Button", "" };
|
||||
cfg::_bool orientation_enabled{ this, "Orientation Enabled", false };
|
||||
|
||||
cfg::string pressure_intensity_button{ this, "Pressure Intensity Button", "" };
|
||||
cfg::uint<0, 100> pressure_intensity{ this, "Pressure Intensity Percent", 50 };
|
||||
cfg::_bool pressure_intensity_toggle_mode{ this, "Pressure Intensity Toggle Mode", false };
|
||||
|
@ -159,6 +159,20 @@ u32 get_axis_keycode(u32 offset, u16 value)
|
||||
}
|
||||
}
|
||||
|
||||
void ps_move_data::reset_sensors()
|
||||
{
|
||||
quaternion = default_quaternion;
|
||||
accelerometer_x = 0.0f;
|
||||
accelerometer_y = 0.0f;
|
||||
accelerometer_z = 0.0f;
|
||||
gyro_x = 0.0f;
|
||||
gyro_y = 0.0f;
|
||||
gyro_z = 0.0f;
|
||||
magnetometer_x = 0.0f;
|
||||
magnetometer_y = 0.0f;
|
||||
magnetometer_z = 0.0f;
|
||||
}
|
||||
|
||||
bool Pad::get_pressure_intensity_button_active(bool is_toggle_mode, u32 player_id)
|
||||
{
|
||||
if (m_pressure_intensity_button_index < 0)
|
||||
@ -238,3 +252,13 @@ bool Pad::get_analog_limiter_button_active(bool is_toggle_mode, u32 player_id)
|
||||
|
||||
return analog_limiter_button.m_pressed;
|
||||
}
|
||||
|
||||
bool Pad::get_orientation_reset_button_active(u32 player_id)
|
||||
{
|
||||
if (m_orientation_reset_button_index < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return m_buttons[m_orientation_reset_button_index].m_pressed;
|
||||
}
|
||||
|
@ -365,7 +365,8 @@ constexpr u32 special_button_offset = 666; // Must not conflict with other CELL
|
||||
enum special_button_value
|
||||
{
|
||||
pressure_intensity,
|
||||
analog_limiter
|
||||
analog_limiter,
|
||||
orientation_reset
|
||||
};
|
||||
|
||||
struct Button
|
||||
@ -470,8 +471,10 @@ struct ps_move_data
|
||||
bool calibration_succeeded = false;
|
||||
|
||||
bool magnetometer_enabled = false;
|
||||
bool orientation_enabled = false;
|
||||
|
||||
std::array<f32, 4> quaternion { 1.0f, 0.0f, 0.0f, 0.0f }; // quaternion orientation (x,y,z,w) of controller relative to default (facing the camera with buttons up)
|
||||
static constexpr std::array<f32, 4> default_quaternion { 1.0f, 0.0f, 0.0f, 0.0f };
|
||||
std::array<f32, 4> quaternion = default_quaternion; // quaternion orientation (x,y,z,w) of controller relative to default (facing the camera with buttons up)
|
||||
f32 accelerometer_x = 0.0f; // linear velocity in m/s²
|
||||
f32 accelerometer_y = 0.0f; // linear velocity in m/s²
|
||||
f32 accelerometer_z = 0.0f; // linear velocity in m/s²
|
||||
@ -482,6 +485,8 @@ struct ps_move_data
|
||||
f32 magnetometer_y = 0.0f;
|
||||
f32 magnetometer_z = 0.0f;
|
||||
s16 temperature = 0;
|
||||
|
||||
void reset_sensors();
|
||||
};
|
||||
|
||||
struct Pad
|
||||
@ -512,6 +517,9 @@ struct Pad
|
||||
bool m_analog_limiter_enabled_last{}; // only used in keyboard_pad_handler
|
||||
bool get_analog_limiter_button_active(bool is_toggle_mode, u32 player_id);
|
||||
|
||||
s32 m_orientation_reset_button_index{-1}; // Special button index. -1 if not set.
|
||||
bool get_orientation_reset_button_active(u32 player_id);
|
||||
|
||||
// Cable State: 0 - 1 plugged in ?
|
||||
u8 m_cable_state{0};
|
||||
|
||||
|
@ -58,6 +58,7 @@ ds3_pad_handler::ds3_pad_handler()
|
||||
b_has_rgb = false;
|
||||
b_has_player_led = true;
|
||||
b_has_pressure_intensity_button = false; // The DS3 obviously already has this feature natively.
|
||||
b_has_orientation = true;
|
||||
|
||||
m_name_string = "DS3 Pad #";
|
||||
m_max_devices = CELL_PAD_MAX_PORT_NUM;
|
||||
@ -199,6 +200,7 @@ void ds3_pad_handler::init_config(cfg_pad* cfg)
|
||||
|
||||
cfg->pressure_intensity_button.def = ::at32(button_list, DS3KeyCodes::None);
|
||||
cfg->analog_limiter_button.def = ::at32(button_list, DS3KeyCodes::None);
|
||||
cfg->orientation_reset_button.def = ::at32(button_list, DS3KeyCodes::None);
|
||||
|
||||
// Set default misc variables
|
||||
cfg->lstick_anti_deadzone.def = 0;
|
||||
@ -462,6 +464,9 @@ void ds3_pad_handler::get_extended_info(const pad_ensemble& binding)
|
||||
//pad->m_sensors[1].m_value = polish_value(pad->m_sensors[1].m_value, 226, 226, 512, 512, 0, 1023);
|
||||
//pad->m_sensors[2].m_value = polish_value(pad->m_sensors[2].m_value, 113, 113, 512, 512, 0, 1023);
|
||||
//pad->m_sensors[3].m_value = polish_value(pad->m_sensors[3].m_value, 1, 1, 512, 512, 0, 1023);
|
||||
|
||||
// Set raw orientation
|
||||
set_raw_orientation(*pad);
|
||||
}
|
||||
|
||||
bool ds3_pad_handler::get_is_left_trigger(const std::shared_ptr<PadDevice>& /*device*/, u64 keyCode)
|
||||
|
@ -118,6 +118,7 @@ ds4_pad_handler::ds4_pad_handler()
|
||||
b_has_rgb = true;
|
||||
b_has_battery = true;
|
||||
b_has_battery_led = true;
|
||||
b_has_orientation = true;
|
||||
|
||||
m_name_string = "DS4 Pad #";
|
||||
m_max_devices = CELL_PAD_MAX_PORT_NUM;
|
||||
@ -179,6 +180,7 @@ void ds4_pad_handler::init_config(cfg_pad* cfg)
|
||||
|
||||
cfg->pressure_intensity_button.def = ::at32(button_list, DS4KeyCodes::None);
|
||||
cfg->analog_limiter_button.def = ::at32(button_list, DS4KeyCodes::None);
|
||||
cfg->orientation_reset_button.def = ::at32(button_list, DS4KeyCodes::None);
|
||||
|
||||
// Set default misc variables
|
||||
cfg->lstick_anti_deadzone.def = static_cast<u32>(0.13 * thumb_max); // 13%
|
||||
@ -883,29 +885,27 @@ void ds4_pad_handler::get_extended_info(const pad_ensemble& binding)
|
||||
|
||||
// these values come already calibrated, all we need to do is convert to ds3 range
|
||||
|
||||
// accel
|
||||
f32 accelX = static_cast<s16>(input.accel[0]) / static_cast<f32>(DS4_ACC_RES_PER_G) * -1;
|
||||
f32 accelY = static_cast<s16>(input.accel[1]) / static_cast<f32>(DS4_ACC_RES_PER_G) * -1;
|
||||
f32 accelZ = static_cast<s16>(input.accel[2]) / static_cast<f32>(DS4_ACC_RES_PER_G) * -1;
|
||||
// acceleration (linear velocity in m/s²)
|
||||
const f32 accel_x = static_cast<s16>(input.accel[0]) / static_cast<f32>(DS4_ACC_RES_PER_G) * -1;
|
||||
const f32 accel_y = static_cast<s16>(input.accel[1]) / static_cast<f32>(DS4_ACC_RES_PER_G) * -1;
|
||||
const f32 accel_z = static_cast<s16>(input.accel[2]) / static_cast<f32>(DS4_ACC_RES_PER_G) * -1;
|
||||
|
||||
// gyro (angular velocity in degree/s)
|
||||
const f32 gyro_x = static_cast<s16>(input.gyro[0]) / static_cast<f32>(DS4_GYRO_RES_PER_DEG_S) * -1;
|
||||
const f32 gyro_y = static_cast<s16>(input.gyro[1]) / static_cast<f32>(DS4_GYRO_RES_PER_DEG_S) * -1;
|
||||
const f32 gyro_z = static_cast<s16>(input.gyro[2]) / static_cast<f32>(DS4_GYRO_RES_PER_DEG_S) * -1;
|
||||
|
||||
// now just use formula from ds3
|
||||
accelX = accelX * 113 + 512;
|
||||
accelY = accelY * 113 + 512;
|
||||
accelZ = accelZ * 113 + 512;
|
||||
|
||||
pad->m_sensors[0].m_value = Clamp0To1023(accelX);
|
||||
pad->m_sensors[1].m_value = Clamp0To1023(accelY);
|
||||
pad->m_sensors[2].m_value = Clamp0To1023(accelZ);
|
||||
|
||||
// gyroY is yaw, which is all that we need
|
||||
//f32 gyroX = static_cast<s16>(input.gyro[0]) / static_cast<f32>(DS4_GYRO_RES_PER_DEG_S) * -1;
|
||||
f32 gyroY = static_cast<s16>(input.gyro[1]) / static_cast<f32>(DS4_GYRO_RES_PER_DEG_S) * -1;
|
||||
//f32 gyroZ = static_cast<s16>(input.gyro[2]) / static_cast<f32>(DS4_GYRO_RES_PER_DEG_S) * -1;
|
||||
pad->m_sensors[0].m_value = Clamp0To1023(accel_x * MOTION_ONE_G + 512);
|
||||
pad->m_sensors[1].m_value = Clamp0To1023(accel_y * MOTION_ONE_G + 512);
|
||||
pad->m_sensors[2].m_value = Clamp0To1023(accel_z * MOTION_ONE_G + 512);
|
||||
|
||||
// gyro_y is yaw, which is all that we need.
|
||||
// Convert to ds3. The ds3 resolution is 123/90°/sec.
|
||||
gyroY = gyroY * (123.f / 90.f) + 512;
|
||||
pad->m_sensors[3].m_value = Clamp0To1023(gyro_y * (123.f / 90.f) + 512);
|
||||
|
||||
pad->m_sensors[3].m_value = Clamp0To1023(gyroY);
|
||||
// Set raw orientation
|
||||
set_raw_orientation(pad->move_data, accel_x, accel_y, accel_z, gyro_x, gyro_y, gyro_z);
|
||||
}
|
||||
|
||||
void ds4_pad_handler::apply_pad_data(const pad_ensemble& binding)
|
||||
|
@ -90,6 +90,7 @@ dualsense_pad_handler::dualsense_pad_handler()
|
||||
b_has_player_led = true;
|
||||
b_has_battery = true;
|
||||
b_has_battery_led = true;
|
||||
b_has_orientation = true;
|
||||
|
||||
m_name_string = "DualSense Pad #";
|
||||
m_max_devices = CELL_PAD_MAX_PORT_NUM;
|
||||
@ -252,6 +253,7 @@ void dualsense_pad_handler::init_config(cfg_pad* cfg)
|
||||
|
||||
cfg->pressure_intensity_button.def = ::at32(button_list, DualSenseKeyCodes::None);
|
||||
cfg->analog_limiter_button.def = ::at32(button_list, DualSenseKeyCodes::None);
|
||||
cfg->orientation_reset_button.def = ::at32(button_list, DualSenseKeyCodes::None);
|
||||
|
||||
// Set default misc variables
|
||||
cfg->lstick_anti_deadzone.def = static_cast<u32>(0.13 * thumb_max); // 13%
|
||||
@ -614,28 +616,27 @@ void dualsense_pad_handler::get_extended_info(const pad_ensemble& binding)
|
||||
|
||||
// these values come already calibrated, all we need to do is convert to ds3 range
|
||||
|
||||
// gyroY is yaw, which is all that we need
|
||||
//f32 gyroX = static_cast<s16>(input.gyro[0]) / static_cast<f32>(DUALSENSE_GYRO_RES_PER_DEG_S) * -1.f;
|
||||
f32 gyroY = static_cast<s16>(input.gyro[1]) / static_cast<f32>(DUALSENSE_GYRO_RES_PER_DEG_S) * -1.f;
|
||||
//f32 gyroZ = static_cast<s16>(input.gyro[2]) / static_cast<f32>(DUALSENSE_GYRO_RES_PER_DEG_S) * -1.f;
|
||||
// gyro (angular velocity in degree/s)
|
||||
const f32 gyro_x = static_cast<s16>(input.gyro[0]) / static_cast<f32>(DUALSENSE_GYRO_RES_PER_DEG_S) * -1.f;
|
||||
const f32 gyro_y = static_cast<s16>(input.gyro[1]) / static_cast<f32>(DUALSENSE_GYRO_RES_PER_DEG_S) * -1.f;
|
||||
const f32 gyro_z = static_cast<s16>(input.gyro[2]) / static_cast<f32>(DUALSENSE_GYRO_RES_PER_DEG_S) * -1.f;
|
||||
|
||||
// accel
|
||||
f32 accelX = static_cast<s16>(input.accel[0]) / static_cast<f32>(DUALSENSE_ACC_RES_PER_G) * -1;
|
||||
f32 accelY = static_cast<s16>(input.accel[1]) / static_cast<f32>(DUALSENSE_ACC_RES_PER_G) * -1;
|
||||
f32 accelZ = static_cast<s16>(input.accel[2]) / static_cast<f32>(DUALSENSE_ACC_RES_PER_G) * -1;
|
||||
// acceleration (linear velocity in m/s²)
|
||||
const f32 accel_x = static_cast<s16>(input.accel[0]) / static_cast<f32>(DUALSENSE_ACC_RES_PER_G) * -1;
|
||||
const f32 accel_y = static_cast<s16>(input.accel[1]) / static_cast<f32>(DUALSENSE_ACC_RES_PER_G) * -1;
|
||||
const f32 accel_z = static_cast<s16>(input.accel[2]) / static_cast<f32>(DUALSENSE_ACC_RES_PER_G) * -1;
|
||||
|
||||
// now just use formula from ds3
|
||||
accelX = accelX * 113 + 512;
|
||||
accelY = accelY * 113 + 512;
|
||||
accelZ = accelZ * 113 + 512;
|
||||
pad->m_sensors[0].m_value = Clamp0To1023(accel_x * MOTION_ONE_G + 512);
|
||||
pad->m_sensors[1].m_value = Clamp0To1023(accel_y * MOTION_ONE_G + 512);
|
||||
pad->m_sensors[2].m_value = Clamp0To1023(accel_z * MOTION_ONE_G + 512);
|
||||
|
||||
// gyro_y is yaw, which is all that we need
|
||||
// Convert to ds3. The ds3 resolution is 123/90°/sec.
|
||||
gyroY = gyroY * (123.f / 90.f) + 512;
|
||||
pad->m_sensors[3].m_value = Clamp0To1023(gyro_y * (123.f / 90.f) + 512);
|
||||
|
||||
pad->m_sensors[0].m_value = Clamp0To1023(accelX);
|
||||
pad->m_sensors[1].m_value = Clamp0To1023(accelY);
|
||||
pad->m_sensors[2].m_value = Clamp0To1023(accelZ);
|
||||
pad->m_sensors[3].m_value = Clamp0To1023(gyroY);
|
||||
// Set raw orientation
|
||||
set_raw_orientation(pad->move_data, accel_x, accel_y, accel_z, gyro_x, gyro_y, gyro_z);
|
||||
}
|
||||
|
||||
std::unordered_map<u64, u16> dualsense_pad_handler::get_button_values(const std::shared_ptr<PadDevice>& device)
|
||||
|
@ -34,6 +34,7 @@ evdev_joystick_handler::evdev_joystick_handler()
|
||||
b_has_rumble = true;
|
||||
b_has_motion = true;
|
||||
b_has_deadzones = true;
|
||||
b_has_orientation = true;
|
||||
|
||||
m_trigger_threshold = trigger_max / 2;
|
||||
m_thumb_threshold = thumb_max / 2;
|
||||
@ -84,6 +85,7 @@ void evdev_joystick_handler::init_config(cfg_pad* cfg)
|
||||
|
||||
cfg->pressure_intensity_button.def = ::at32(button_list, NO_BUTTON);
|
||||
cfg->analog_limiter_button.def = ::at32(button_list, NO_BUTTON);
|
||||
cfg->orientation_reset_button.def = ::at32(button_list, NO_BUTTON);
|
||||
|
||||
// Set default misc variables
|
||||
cfg->lstick_anti_deadzone.def = static_cast<u32>(0.13 * thumb_max); // 13%
|
||||
@ -1075,6 +1077,8 @@ void evdev_joystick_handler::get_extended_info(const pad_ensemble& binding)
|
||||
}
|
||||
}
|
||||
|
||||
set_raw_orientation(*pad);
|
||||
|
||||
if (ret < 0)
|
||||
{
|
||||
// -EAGAIN signifies no available events, not an actual *error*.
|
||||
@ -1382,6 +1386,12 @@ bool evdev_joystick_handler::bindPadToDevice(std::shared_ptr<Pad> pad)
|
||||
pad->m_analog_limiter_button_index = static_cast<s32>(pad->m_buttons.size()) - 1;
|
||||
}
|
||||
|
||||
if (b_has_orientation)
|
||||
{
|
||||
pad->m_buttons.emplace_back(special_button_offset, find_buttons(cfg->orientation_reset_button), special_button_value::orientation_reset);
|
||||
pad->m_orientation_reset_button_index = static_cast<s32>(pad->m_buttons.size()) - 1;
|
||||
}
|
||||
|
||||
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2,find_buttons(cfg->triangle), CELL_PAD_CTRL_TRIANGLE);
|
||||
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2,find_buttons(cfg->circle), CELL_PAD_CTRL_CIRCLE);
|
||||
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2,find_buttons(cfg->cross), CELL_PAD_CTRL_CROSS);
|
||||
|
@ -127,6 +127,7 @@ ps_move_handler::ps_move_handler()
|
||||
b_has_battery = true;
|
||||
b_has_battery_led = false;
|
||||
b_has_pressure_intensity_button = false;
|
||||
b_has_orientation = true;
|
||||
|
||||
m_name_string = "PS Move #";
|
||||
m_max_devices = 4; // CELL_GEM_MAX_NUM
|
||||
@ -170,6 +171,8 @@ void ps_move_handler::init_config(cfg_pad* cfg)
|
||||
cfg->l2.def = ::at32(button_list, ps_move_key_codes::none);
|
||||
cfg->l3.def = ::at32(button_list, ps_move_key_codes::none);
|
||||
|
||||
cfg->orientation_reset_button.def = ::at32(button_list, ps_move_key_codes::none);
|
||||
|
||||
// Set default misc variables
|
||||
cfg->lstickdeadzone.def = 40; // between 0 and 255
|
||||
cfg->rstickdeadzone.def = 40; // between 0 and 255
|
||||
@ -359,8 +362,6 @@ void ps_move_handler::check_add_device(hid_device* hidDevice, std::string_view p
|
||||
psmove_parse_calibration(calibration, *device);
|
||||
}
|
||||
|
||||
device->reset_orientation();
|
||||
|
||||
// Activate
|
||||
if (send_output_report(device) == -1)
|
||||
{
|
||||
@ -669,12 +670,12 @@ void ps_move_handler::get_extended_info(const pad_ensemble& binding)
|
||||
// The default position is flat on the ground, pointing forward.
|
||||
// The accelerometers constantly measure G forces.
|
||||
// The gyros measure changes in orientation and will reset when the device isn't moved anymore.
|
||||
s16 accel_x = input.accel_x_1; // Increases if the device is rolled to the left
|
||||
s16 accel_y = input.accel_y_1; // Increases if the device is pitched upwards
|
||||
s16 accel_z = input.accel_z_1; // Increases if the device is moved upwards
|
||||
s16 gyro_x = input.gyro_x_1; // Increases if the device is pitched upwards
|
||||
s16 gyro_y = input.gyro_y_1; // Increases if the device is rolled to the right
|
||||
s16 gyro_z = input.gyro_z_1; // Increases if the device is yawed to the left
|
||||
f32 accel_x = input.accel_x_1; // Increases if the device is rolled to the left
|
||||
f32 accel_y = input.accel_y_1; // Increases if the device is pitched upwards
|
||||
f32 accel_z = input.accel_z_1; // Increases if the device is moved upwards
|
||||
f32 gyro_x = input.gyro_x_1; // Increases if the device is pitched upwards
|
||||
f32 gyro_y = input.gyro_y_1; // Increases if the device is rolled to the right
|
||||
f32 gyro_z = input.gyro_z_1; // Increases if the device is yawed to the left
|
||||
|
||||
if (dev->model == ps_move_model::ZCM1)
|
||||
{
|
||||
@ -684,45 +685,60 @@ void ps_move_handler::get_extended_info(const pad_ensemble& binding)
|
||||
gyro_x -= zero_shift;
|
||||
gyro_y -= zero_shift;
|
||||
gyro_z -= zero_shift;
|
||||
|
||||
const ps_move_input_report_ZCM1& input_zcm1 = dev->input_report_ZCM1;
|
||||
|
||||
#define TWELVE_BIT_SIGNED(x) (((x) & 0x800) ? (-(((~(x)) & 0xFFF) + 1)) : (x))
|
||||
pad->move_data.magnetometer_x = static_cast<f32>(TWELVE_BIT_SIGNED(((input.magnetometer_x & 0x0F) << 8) | input_zcm1.magnetometer_x2));
|
||||
pad->move_data.magnetometer_y = static_cast<f32>(TWELVE_BIT_SIGNED((input_zcm1.magnetometer_y << 4) | (input_zcm1.magnetometer_yz & 0xF0) >> 4));
|
||||
pad->move_data.magnetometer_z = static_cast<f32>(TWELVE_BIT_SIGNED(((input_zcm1.magnetometer_yz & 0x0F) << 8) | input_zcm1.magnetometer_z));
|
||||
}
|
||||
|
||||
// Apply calibration
|
||||
if (dev->calibration.is_valid)
|
||||
if (!device->config || !device->config->orientation_enabled)
|
||||
{
|
||||
pad->move_data.accelerometer_x = accel_x * dev->calibration.accel_x_factor + dev->calibration.accel_x_offset;
|
||||
pad->move_data.accelerometer_y = accel_y * dev->calibration.accel_y_factor + dev->calibration.accel_y_offset;
|
||||
pad->move_data.accelerometer_z = accel_z * dev->calibration.accel_z_factor + dev->calibration.accel_z_offset;
|
||||
pad->move_data.gyro_x = (gyro_x - dev->calibration.gyro_x_offset) * dev->calibration.gyro_x_gain;
|
||||
pad->move_data.gyro_y = (gyro_y - dev->calibration.gyro_y_offset) * dev->calibration.gyro_y_gain;
|
||||
pad->move_data.gyro_z = (gyro_z - dev->calibration.gyro_z_offset) * dev->calibration.gyro_z_gain;
|
||||
pad->move_data.reset_sensors();
|
||||
}
|
||||
else
|
||||
{
|
||||
constexpr f32 MOVE_ONE_G = 4096.0f; // This is just a rough estimate and probably depends on the device
|
||||
// Apply calibration
|
||||
if (dev->calibration.is_valid)
|
||||
{
|
||||
accel_x = accel_x * dev->calibration.accel_x_factor + dev->calibration.accel_x_offset;
|
||||
accel_y = accel_y * dev->calibration.accel_y_factor + dev->calibration.accel_y_offset;
|
||||
accel_z = accel_z * dev->calibration.accel_z_factor + dev->calibration.accel_z_offset;
|
||||
gyro_x = (gyro_x - dev->calibration.gyro_x_offset) * dev->calibration.gyro_x_gain;
|
||||
gyro_y = (gyro_y - dev->calibration.gyro_y_offset) * dev->calibration.gyro_y_gain;
|
||||
gyro_z = (gyro_z - dev->calibration.gyro_z_offset) * dev->calibration.gyro_z_gain;
|
||||
}
|
||||
else
|
||||
{
|
||||
constexpr f32 MOVE_ONE_G = 4096.0f; // This is just a rough estimate and probably depends on the device
|
||||
|
||||
pad->move_data.accelerometer_x = accel_x / MOVE_ONE_G;
|
||||
pad->move_data.accelerometer_y = accel_y / MOVE_ONE_G;
|
||||
pad->move_data.accelerometer_z = accel_z / MOVE_ONE_G;
|
||||
pad->move_data.gyro_x = gyro_x / MOVE_ONE_G;
|
||||
pad->move_data.gyro_y = gyro_y / MOVE_ONE_G;
|
||||
pad->move_data.gyro_z = gyro_z / MOVE_ONE_G;
|
||||
accel_x /= MOVE_ONE_G;
|
||||
accel_y /= MOVE_ONE_G;
|
||||
accel_z /= MOVE_ONE_G;
|
||||
gyro_x /= MOVE_ONE_G;
|
||||
gyro_y /= MOVE_ONE_G;
|
||||
gyro_z /= MOVE_ONE_G;
|
||||
}
|
||||
|
||||
pad->move_data.accelerometer_x = accel_x;
|
||||
pad->move_data.accelerometer_y = accel_y;
|
||||
pad->move_data.accelerometer_z = accel_z;
|
||||
pad->move_data.gyro_x = gyro_x;
|
||||
pad->move_data.gyro_y = gyro_y;
|
||||
pad->move_data.gyro_z = gyro_z;
|
||||
|
||||
if (dev->model == ps_move_model::ZCM1)
|
||||
{
|
||||
const ps_move_input_report_ZCM1& input_zcm1 = dev->input_report_ZCM1;
|
||||
|
||||
#define TWELVE_BIT_SIGNED(x) (((x) & 0x800) ? (-(((~(x)) & 0xFFF) + 1)) : (x))
|
||||
pad->move_data.magnetometer_x = static_cast<f32>(TWELVE_BIT_SIGNED(((input.magnetometer_x & 0x0F) << 8) | input_zcm1.magnetometer_x2));
|
||||
pad->move_data.magnetometer_y = static_cast<f32>(TWELVE_BIT_SIGNED((input_zcm1.magnetometer_y << 4) | (input_zcm1.magnetometer_yz & 0xF0) >> 4));
|
||||
pad->move_data.magnetometer_z = static_cast<f32>(TWELVE_BIT_SIGNED(((input_zcm1.magnetometer_yz & 0x0F) << 8) | input_zcm1.magnetometer_z));
|
||||
}
|
||||
}
|
||||
|
||||
pad->move_data.temperature = ((input.temperature << 4) | ((input.magnetometer_x & 0xF0) >> 4));
|
||||
|
||||
pad->m_sensors[0].m_value = Clamp0To1023(512.0f + (MOTION_ONE_G * pad->move_data.accelerometer_x * -1.0f));
|
||||
pad->m_sensors[1].m_value = Clamp0To1023(512.0f + (MOTION_ONE_G * pad->move_data.accelerometer_y * -1.0f));
|
||||
pad->m_sensors[2].m_value = Clamp0To1023(512.0f + (MOTION_ONE_G * pad->move_data.accelerometer_z));
|
||||
pad->m_sensors[3].m_value = Clamp0To1023(512.0f + (MOTION_ONE_G * pad->move_data.gyro_z * -1.0f));
|
||||
|
||||
dev->update_orientation(pad->move_data);
|
||||
pad->m_sensors[0].m_value = Clamp0To1023(512.0f + (MOTION_ONE_G * accel_x * -1.0f));
|
||||
pad->m_sensors[1].m_value = Clamp0To1023(512.0f + (MOTION_ONE_G * accel_y * -1.0f));
|
||||
pad->m_sensors[2].m_value = Clamp0To1023(512.0f + (MOTION_ONE_G * accel_z));
|
||||
pad->m_sensors[3].m_value = Clamp0To1023(512.0f + (MOTION_ONE_G * gyro_z * -1.0f));
|
||||
|
||||
handle_external_device(binding);
|
||||
}
|
||||
@ -863,74 +879,3 @@ u32 ps_move_handler::get_battery_level(const std::string& padId)
|
||||
// 0 to 5
|
||||
return std::clamp<u32>(device->battery_level * 20, 0, 100);
|
||||
}
|
||||
|
||||
void ps_move_device::reset_orientation()
|
||||
{
|
||||
// Initialize Fusion
|
||||
ahrs = {};
|
||||
FusionAhrsInitialise(&ahrs);
|
||||
ahrs.settings.convention = FusionConvention::FusionConventionEnu;
|
||||
ahrs.settings.gain = 0.0f; // If gain is set, the algorithm tries to adjust the orientation over time.
|
||||
FusionAhrsSetSettings(&ahrs, &ahrs.settings);
|
||||
FusionAhrsReset(&ahrs);
|
||||
}
|
||||
|
||||
void ps_move_device::update_orientation(ps_move_data& move_data)
|
||||
{
|
||||
if (move_data.calibration_requested)
|
||||
{
|
||||
reset_orientation();
|
||||
|
||||
move_data.calibration_succeeded = true;
|
||||
}
|
||||
|
||||
// Get elapsed time since last update
|
||||
const u64 now_us = get_system_time();
|
||||
const float elapsed_sec = (last_ahrs_update_time_us == 0) ? 0.0f : ((now_us - last_ahrs_update_time_us) / 1'000'000.0f);
|
||||
last_ahrs_update_time_us = now_us;
|
||||
|
||||
// The ps move handler's axis may differ from the Fusion axis, so we have to map them correctly.
|
||||
// Don't ask how the axis work. It's basically been trial and error.
|
||||
ensure(ahrs.settings.convention == FusionConvention::FusionConventionEnu); // East-North-Up
|
||||
|
||||
const FusionVector accelerometer{
|
||||
.axis {
|
||||
.x = -move_data.accelerometer_x,
|
||||
.y = +move_data.accelerometer_y,
|
||||
.z = +move_data.accelerometer_z
|
||||
}
|
||||
};
|
||||
|
||||
static constexpr f32 PI = 3.14159265f;
|
||||
const auto rad_to_degree = [](f32 radians) -> f32 { return radians * 180.0f / PI; };
|
||||
const FusionVector gyroscope{
|
||||
.axis {
|
||||
.x = +rad_to_degree(move_data.gyro_x),
|
||||
.y = +rad_to_degree(move_data.gyro_z),
|
||||
.z = -rad_to_degree(move_data.gyro_y)
|
||||
}
|
||||
};
|
||||
|
||||
FusionVector magnetometer {};
|
||||
|
||||
if (move_data.magnetometer_enabled)
|
||||
{
|
||||
magnetometer = FusionVector{
|
||||
.axis {
|
||||
.x = move_data.magnetometer_x,
|
||||
.y = move_data.magnetometer_y,
|
||||
.z = move_data.magnetometer_z
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Update Fusion
|
||||
FusionAhrsUpdate(&ahrs, gyroscope, accelerometer, magnetometer, elapsed_sec);
|
||||
|
||||
// Get quaternion
|
||||
const FusionQuaternion quaternion = FusionAhrsGetQuaternion(&ahrs);
|
||||
move_data.quaternion[0] = quaternion.array[1];
|
||||
move_data.quaternion[1] = quaternion.array[2];
|
||||
move_data.quaternion[2] = quaternion.array[3];
|
||||
move_data.quaternion[3] = quaternion.array[0];
|
||||
}
|
||||
|
@ -146,12 +146,6 @@ public:
|
||||
u32 external_device_id = 0;
|
||||
ps_move_calibration calibration{};
|
||||
|
||||
FusionAhrs ahrs {}; // Used to calculate quaternions from sensor data
|
||||
u64 last_ahrs_update_time_us = 0; // Last ahrs update
|
||||
|
||||
void update_orientation(ps_move_data& move_data);
|
||||
void reset_orientation();
|
||||
|
||||
const reports::ps_move_input_report_common& input_report_common() const;
|
||||
};
|
||||
|
||||
|
@ -179,6 +179,7 @@ sdl_pad_handler::sdl_pad_handler() : PadHandlerBase(pad_handler::sdl)
|
||||
b_has_rgb = true;
|
||||
b_has_battery = true;
|
||||
b_has_battery_led = true;
|
||||
b_has_orientation = true;
|
||||
|
||||
m_trigger_threshold = trigger_max / 2;
|
||||
m_thumb_threshold = thumb_max / 2;
|
||||
@ -233,6 +234,7 @@ void sdl_pad_handler::init_config(cfg_pad* cfg)
|
||||
|
||||
cfg->pressure_intensity_button.def = ::at32(button_list, SDLKeyCodes::None);
|
||||
cfg->analog_limiter_button.def = ::at32(button_list, SDLKeyCodes::None);
|
||||
cfg->orientation_reset_button.def = ::at32(button_list, SDLKeyCodes::None);
|
||||
|
||||
// Set default misc variables
|
||||
cfg->lstick_anti_deadzone.def = static_cast<u32>(0.13 * thumb_max); // 13%
|
||||
@ -732,14 +734,14 @@ void sdl_pad_handler::get_extended_info(const pad_ensemble& binding)
|
||||
}
|
||||
else
|
||||
{
|
||||
const float& accel_x = dev->values_accel[0]; // Angular speed around the x axis (pitch)
|
||||
const float& accel_y = dev->values_accel[1]; // Angular speed around the y axis (yaw)
|
||||
const float& accel_z = dev->values_accel[2]; // Angular speed around the z axis (roll
|
||||
const f32 accel_x = dev->values_accel[0]; // Angular speed around the x axis (pitch)
|
||||
const f32 accel_y = dev->values_accel[1]; // Angular speed around the y axis (yaw)
|
||||
const f32 accel_z = dev->values_accel[2]; // Angular speed around the z axis (roll
|
||||
|
||||
// Convert to ds3. The ds3 resolution is 113/G.
|
||||
pad->m_sensors[0].m_value = Clamp0To1023((accel_x / SDL_STANDARD_GRAVITY) * -1 * 113 + 512);
|
||||
pad->m_sensors[1].m_value = Clamp0To1023((accel_y / SDL_STANDARD_GRAVITY) * -1 * 113 + 512);
|
||||
pad->m_sensors[2].m_value = Clamp0To1023((accel_z / SDL_STANDARD_GRAVITY) * -1 * 113 + 512);
|
||||
pad->m_sensors[0].m_value = Clamp0To1023((accel_x / SDL_STANDARD_GRAVITY) * -1 * MOTION_ONE_G + 512);
|
||||
pad->m_sensors[1].m_value = Clamp0To1023((accel_y / SDL_STANDARD_GRAVITY) * -1 * MOTION_ONE_G + 512);
|
||||
pad->m_sensors[2].m_value = Clamp0To1023((accel_z / SDL_STANDARD_GRAVITY) * -1 * MOTION_ONE_G + 512);
|
||||
}
|
||||
}
|
||||
|
||||
@ -751,16 +753,20 @@ void sdl_pad_handler::get_extended_info(const pad_ensemble& binding)
|
||||
}
|
||||
else
|
||||
{
|
||||
//const float& gyro_x = dev->values_gyro[0]; // Angular speed around the x axis (pitch)
|
||||
const float& gyro_y = dev->values_gyro[1]; // Angular speed around the y axis (yaw)
|
||||
//const float& gyro_z = dev->values_gyro[2]; // Angular speed around the z axis (roll)
|
||||
//const f32 gyro_x = dev->values_gyro[0]; // Angular speed around the x axis (pitch)
|
||||
const f32 gyro_y = dev->values_gyro[1]; // Angular speed around the y axis (yaw)
|
||||
//const f32 gyro_z = dev->values_gyro[2]; // Angular speed around the z axis (roll)
|
||||
|
||||
// Convert to ds3. The ds3 resolution is 123/90°/sec. The SDL gyro is measured in rad/sec.
|
||||
static constexpr f32 PI = 3.14159265f;
|
||||
const float degree = (gyro_y * 180.0f / PI);
|
||||
const f32 degree = rad_to_degree(gyro_y);
|
||||
pad->m_sensors[3].m_value = Clamp0To1023(degree * (123.f / 90.f) + 512);
|
||||
}
|
||||
}
|
||||
|
||||
if (dev->sdl.has_accel || dev->sdl.has_gyro)
|
||||
{
|
||||
set_raw_orientation(*pad);
|
||||
}
|
||||
}
|
||||
|
||||
void sdl_pad_handler::get_motion_sensors(const std::string& pad_id, const motion_callback& callback, const motion_fail_callback& fail_callback, motion_preview_values preview_values, const std::array<AnalogSensor, 4>& sensors)
|
||||
|
@ -46,8 +46,8 @@ public:
|
||||
bool has_accel = false;
|
||||
bool has_gyro = false;
|
||||
|
||||
float data_rate_accel = 0.0f;
|
||||
float data_rate_gyro = 0.0f;
|
||||
f32 data_rate_accel = 0.0f;
|
||||
f32 data_rate_gyro = 0.0f;
|
||||
|
||||
std::set<SDL_GameControllerButton> button_ids;
|
||||
std::set<SDL_GameControllerAxis> axis_ids;
|
||||
@ -57,8 +57,8 @@ public:
|
||||
|
||||
sdl_info sdl{};
|
||||
|
||||
std::array<float, 3> values_accel{};
|
||||
std::array<float, 3> values_gyro{};
|
||||
std::array<f32, 3> values_accel{};
|
||||
std::array<f32, 3> values_gyro{};
|
||||
|
||||
bool led_needs_update = true;
|
||||
bool led_is_on = true;
|
||||
|
@ -84,6 +84,7 @@ skateboard_pad_handler::skateboard_pad_handler()
|
||||
b_has_battery = false;
|
||||
b_has_battery_led = false;
|
||||
b_has_pressure_intensity_button = false;
|
||||
b_has_orientation = true;
|
||||
|
||||
m_name_string = "Skateboard #";
|
||||
m_max_devices = CELL_PAD_MAX_PORT_NUM;
|
||||
@ -134,6 +135,8 @@ void skateboard_pad_handler::init_config(cfg_pad* cfg)
|
||||
cfg->tilt_left.def = ::at32(button_list, skateboard_key_codes::tilt_left);
|
||||
cfg->tilt_right.def = ::at32(button_list, skateboard_key_codes::tilt_right);
|
||||
|
||||
cfg->orientation_reset_button.def = ::at32(button_list, skateboard_key_codes::none);
|
||||
|
||||
// Set default misc variables
|
||||
cfg->lstick_anti_deadzone.def = 0;
|
||||
cfg->rstick_anti_deadzone.def = 0;
|
||||
@ -323,6 +326,8 @@ void skateboard_pad_handler::get_extended_info(const pad_ensemble& binding)
|
||||
pad->m_sensors[1].m_value = Clamp0To1023(input.large_axes[1]);
|
||||
pad->m_sensors[2].m_value = Clamp0To1023(input.large_axes[2]);
|
||||
pad->m_sensors[3].m_value = Clamp0To1023(input.large_axes[3]);
|
||||
|
||||
set_raw_orientation(*pad);
|
||||
}
|
||||
|
||||
pad_preview_values skateboard_pad_handler::get_preview_values(const std::unordered_map<u64, u16>& /*data*/)
|
||||
|
@ -64,6 +64,7 @@ xinput_pad_handler::xinput_pad_handler() : PadHandlerBase(pad_handler::xinput)
|
||||
b_has_deadzones = true;
|
||||
b_has_battery = true;
|
||||
b_has_battery_led = false;
|
||||
b_has_orientation = false;
|
||||
|
||||
m_name_string = "XInput Pad #";
|
||||
m_max_devices = XUSER_MAX_COUNT;
|
||||
@ -119,6 +120,7 @@ void xinput_pad_handler::init_config(cfg_pad* cfg)
|
||||
|
||||
cfg->pressure_intensity_button.def = ::at32(button_list, XInputKeyCodes::None);
|
||||
cfg->analog_limiter_button.def = ::at32(button_list, XInputKeyCodes::None);
|
||||
cfg->orientation_reset_button.def = ::at32(button_list, XInputKeyCodes::None);
|
||||
|
||||
// Set default misc variables
|
||||
cfg->lstick_anti_deadzone.def = static_cast<u32>(0.13 * thumb_max); // 13%
|
||||
@ -413,6 +415,8 @@ bool xinput_pad_handler::Init()
|
||||
}
|
||||
}
|
||||
|
||||
b_has_orientation = !!xinputGetCustomData;
|
||||
|
||||
if (!m_is_init)
|
||||
return false;
|
||||
|
||||
@ -551,6 +555,8 @@ void xinput_pad_handler::get_extended_info(const pad_ensemble& binding)
|
||||
pad->m_sensors[1].m_value = sensors.SCP_ACCEL_Y;
|
||||
pad->m_sensors[2].m_value = sensors.SCP_ACCEL_Z;
|
||||
pad->m_sensors[3].m_value = sensors.SCP_GYRO;
|
||||
|
||||
set_raw_orientation(*pad);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -312,6 +312,7 @@ void pad_settings_dialog::InitButtons()
|
||||
|
||||
insert_button(button_ids::id_pressure_intensity, ui->b_pressure_intensity);
|
||||
insert_button(button_ids::id_analog_limiter, ui->b_analog_limiter);
|
||||
insert_button(button_ids::id_orientation_reset, ui->b_orientation_reset);
|
||||
|
||||
m_pad_buttons->addButton(ui->b_refresh, button_ids::id_refresh);
|
||||
m_pad_buttons->addButton(ui->b_addConfig, button_ids::id_add_config_file);
|
||||
@ -720,6 +721,7 @@ void pad_settings_dialog::ReloadButtons()
|
||||
|
||||
updateButton(button_ids::id_pressure_intensity, ui->b_pressure_intensity, &cfg.pressure_intensity_button);
|
||||
updateButton(button_ids::id_analog_limiter, ui->b_analog_limiter, &cfg.analog_limiter_button);
|
||||
updateButton(button_ids::id_orientation_reset, ui->b_orientation_reset, &cfg.orientation_reset_button);
|
||||
|
||||
UpdateLabels(true);
|
||||
}
|
||||
@ -1195,6 +1197,9 @@ void pad_settings_dialog::UpdateLabels(bool is_reset)
|
||||
RepaintPreviewLabel(ui->preview_stick_left, ui->slider_stick_left->value(), ui->anti_deadzone_slider_stick_left->value(), ui->slider_stick_left->size().width(), m_lx, m_ly, cfg.lpadsquircling, cfg.lstickmultiplier / 100.0);
|
||||
RepaintPreviewLabel(ui->preview_stick_right, ui->slider_stick_right->value(), ui->anti_deadzone_slider_stick_right->value(), ui->slider_stick_right->size().width(), m_rx, m_ry, cfg.rpadsquircling, cfg.rstickmultiplier / 100.0);
|
||||
|
||||
// Update orientation toggle
|
||||
ui->cb_orientation_toggle->setChecked(cfg.orientation_enabled.get());
|
||||
|
||||
// Update analog limiter toggle mode
|
||||
ui->cb_analog_limiter_toggle_mode->setChecked(cfg.analog_limiter_toggle_mode.get());
|
||||
|
||||
@ -1249,6 +1254,7 @@ void pad_settings_dialog::SwitchButtons(bool is_enabled)
|
||||
ui->gb_pressure_intensity_deadzone->setEnabled(is_enabled);
|
||||
ui->gb_pressure_intensity->setEnabled(is_enabled && m_enable_pressure_intensity_button);
|
||||
ui->gb_analog_limiter->setEnabled(is_enabled && m_enable_analog_limiter_button);
|
||||
ui->gb_orientation_reset->setEnabled(is_enabled && m_enable_orientation_reset_button);
|
||||
ui->gb_vibration->setEnabled(is_enabled && m_enable_rumble);
|
||||
ui->gb_motion_controls->setEnabled(is_enabled && m_enable_motion);
|
||||
ui->gb_stick_deadzones->setEnabled(is_enabled && m_enable_deadzones);
|
||||
@ -1467,11 +1473,15 @@ void pad_settings_dialog::ChangeHandler()
|
||||
// Enable Analog Limiter Settings
|
||||
m_enable_analog_limiter_button = m_handler->has_analog_limiter_button();
|
||||
|
||||
// Enable Orientation Reset Settings
|
||||
m_enable_orientation_reset_button = m_handler->has_orientation();
|
||||
|
||||
// Change our contextual widgets
|
||||
ui->left_stack->setCurrentIndex((m_handler->m_type == pad_handler::keyboard) ? 1 : 0);
|
||||
ui->right_stack->setCurrentIndex((m_handler->m_type == pad_handler::keyboard) ? 1 : 0);
|
||||
ui->gb_pressure_intensity->setVisible(m_handler->has_pressure_intensity_button());
|
||||
ui->gb_analog_limiter->setVisible(m_handler->has_analog_limiter_button());
|
||||
ui->gb_orientation_reset->setVisible(m_handler->has_orientation());
|
||||
|
||||
// Update device dropdown and block signals while doing so
|
||||
ui->chooseDevice->blockSignals(true);
|
||||
@ -1829,8 +1839,11 @@ void pad_settings_dialog::ApplyCurrentPlayerConfig(int new_player_id)
|
||||
for (const auto& [id, button] : m_cfg_entries)
|
||||
{
|
||||
// Let's ignore special keys, unless we're using a keyboard
|
||||
if ((id == button_ids::id_pressure_intensity || id == button_ids::id_analog_limiter) && m_handler->m_type != pad_handler::keyboard)
|
||||
if (m_handler->m_type != pad_handler::keyboard &&
|
||||
(id == button_ids::id_pressure_intensity || id == button_ids::id_analog_limiter || id == button_ids::id_orientation_reset))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const std::string& key : cfg_pad::get_buttons(button.keys))
|
||||
{
|
||||
@ -1886,6 +1899,11 @@ void pad_settings_dialog::ApplyCurrentPlayerConfig(int new_player_id)
|
||||
cfg.pressure_intensity_toggle_mode.set(ui->cb_pressure_intensity_toggle_mode->isChecked());
|
||||
}
|
||||
|
||||
if (m_handler->has_orientation())
|
||||
{
|
||||
cfg.orientation_enabled.set(ui->cb_orientation_toggle->isChecked());
|
||||
}
|
||||
|
||||
cfg.pressure_intensity_deadzone.set(ui->pressure_intensity_deadzone->value());
|
||||
|
||||
if (m_handler->m_type == pad_handler::keyboard)
|
||||
@ -2077,6 +2095,7 @@ void pad_settings_dialog::SubscribeTooltips()
|
||||
// Localized tooltips
|
||||
const Tooltips tooltips;
|
||||
|
||||
SubscribeTooltip(ui->gb_orientation_reset, tooltips.gamepad_settings.orientation_reset);
|
||||
SubscribeTooltip(ui->gb_analog_limiter, tooltips.gamepad_settings.analog_limiter);
|
||||
SubscribeTooltip(ui->gb_pressure_intensity, tooltips.gamepad_settings.pressure_intensity);
|
||||
SubscribeTooltip(ui->gb_pressure_intensity_deadzone, tooltips.gamepad_settings.pressure_deadzone);
|
||||
|
@ -66,6 +66,7 @@ class pad_settings_dialog : public QDialog
|
||||
|
||||
id_pressure_intensity, // Special button for pressure intensity
|
||||
id_analog_limiter, // Special button for analog limiter
|
||||
id_orientation_reset, // Special button for orientation reset
|
||||
|
||||
id_pad_end, // end
|
||||
|
||||
@ -123,6 +124,7 @@ private:
|
||||
bool m_enable_motion{ false };
|
||||
bool m_enable_pressure_intensity_button{ true };
|
||||
bool m_enable_analog_limiter_button{ true };
|
||||
bool m_enable_orientation_reset_button{ true };
|
||||
|
||||
// Button Mapping
|
||||
QButtonGroup* m_pad_buttons = nullptr;
|
||||
|
@ -36,9 +36,9 @@
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>-71</y>
|
||||
<width>1290</width>
|
||||
<height>907</height>
|
||||
<y>0</y>
|
||||
<width>1292</width>
|
||||
<height>902</height>
|
||||
</rect>
|
||||
</property>
|
||||
<widget class="QWidget" name="tab">
|
||||
@ -669,6 +669,57 @@
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="gb_orientation_reset">
|
||||
<property name="title">
|
||||
<string>Orientation Reset</string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="orientation_reset_layout" stretch="2,1,1">
|
||||
<property name="leftMargin">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QPushButton" name="b_orientation_reset">
|
||||
<property name="text">
|
||||
<string>-</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="spacer_orientation_reset">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Policy::MinimumExpanding</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="cb_orientation_toggle">
|
||||
<property name="text">
|
||||
<string>Orientation</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="gb_analog_limiter">
|
||||
<property name="title">
|
||||
@ -695,12 +746,12 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<spacer name="spacer_analog_limiter">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::MinimumExpanding</enum>
|
||||
<enum>QSizePolicy::Policy::MinimumExpanding</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
@ -2286,7 +2337,7 @@
|
||||
<item>
|
||||
<spacer name="spacer_squircle_left">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
@ -2316,7 +2367,7 @@
|
||||
<item>
|
||||
<spacer name="spacer_squircle_right">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
@ -2364,7 +2415,7 @@
|
||||
<item>
|
||||
<spacer name="spacer_stick_multi_left">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
@ -2391,7 +2442,7 @@
|
||||
<item>
|
||||
<spacer name="spacer_stick_multi_right">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
|
@ -287,6 +287,7 @@ public:
|
||||
const QString mmjoy = tr("The MMJoystick handler should work with almost any controller recognized by Windows. However, it is recommended that you use the more specific handlers if you have a controller that supports them.");
|
||||
const QString sdl = tr("The SDL handler supports a variety of controllers across different platforms.");
|
||||
|
||||
const QString orientation_reset = tr("Resets the sensor orientation when pressed.<br>Toggle the checkbox to enable or disable the orientation feature.<br>Currently only used for PS Move interactions.");
|
||||
const QString analog_limiter = tr("Applies the stick multipliers while this special button is pressed.<br>Enable \"Toggle\" if you want to toggle the analog limiter on button press instead.");
|
||||
const QString pressure_intensity = tr("Controls the intensity of pressure sensitive buttons while this special button is pressed.<br>Enable \"Toggle\" if you want to toggle the intensity on button press instead.<br>Use the percentage to change how hard you want to press a button.");
|
||||
const QString pressure_deadzone = tr("Controls the deadzone of pressure sensitive buttons. It determines how far the button has to be pressed until it is recognized by the game. The resulting range will be projected onto the full button sensitivity range.");
|
||||
|
Loading…
x
Reference in New Issue
Block a user