GUI + calibration + multiple sources

Added proper QT GUI and proper multiple controller support.
Calibration using offsets and sensitivity as a scale factor has also
been added.
This commit is contained in:
anirudhb
2020-05-02 15:15:52 -07:00
parent bf03e64491
commit 79e5d0e96b
10 changed files with 1154 additions and 448 deletions

View File

@@ -236,8 +236,12 @@ void Controller_NPad::OnLoadInputDevices() {
std::transform(players[i].analogs.begin() + Settings::NativeAnalog::STICK_HID_BEGIN,
players[i].analogs.begin() + Settings::NativeAnalog::STICK_HID_END,
sticks[i].begin(), Input::CreateDevice<Input::AnalogDevice>);
std::transform(players[i].motion_devices.begin(), players[i].motion_devices.end(),
motion_sensors[i].begin(), [](const Settings::MotionRaw& raw) {
return raw.enabled ? Input::CreateDevice<Input::MotionDevice>(raw.device)
: nullptr;
});
}
motion_sensors[0] = Input::CreateDevice<Input::MotionDevice>(Settings::values.motion_device);
}
void Controller_NPad::OnRelease() {}
@@ -369,19 +373,16 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8*
auto& pad_state = npad_pad_states[npad_index];
// Try to get motion sensor state if it exists
Common::Vec3f accel, gyro;
if (sixaxis_sensor_enabled && motion_sensors[i]) {
std::tie(accel, gyro) = motion_sensors[i]->GetStatus();
#if 1
std::ostringstream builder{};
builder << "Got gyro x=";
builder << gyro.x;
builder << " y=";
builder << gyro.y;
builder << " z=";
builder << gyro.z;
LOG_INFO(Service_HID, builder.str().c_str());
#endif
Common::Vec3f accel1, gyro1, accel2, gyro2;
if (sixaxis_sensor_enabled) {
const auto& sensor1 = motion_sensors[i][0];
const auto& sensor2 = motion_sensors[i][1];
if (sensor1) {
std::tie(accel1, gyro1) = sensor1->GetStatus();
}
if (sensor2) {
std::tie(accel2, gyro2) = sensor2->GetStatus();
}
}
auto& main_controller =
@@ -413,9 +414,9 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8*
handheld_entry.pad.l_stick = pad_state.l_stick;
handheld_entry.pad.r_stick = pad_state.r_stick;
if (sixaxis_sensor_enabled && motion_sensors[i]) {
npad.handheld.sixaxis[npad.handheld.common.last_entry_index].accelerometer = accel;
npad.handheld.sixaxis[npad.handheld.common.last_entry_index].gyroscope = gyro;
if (sixaxis_sensor_enabled && motion_sensors[i][0]) {
npad.handheld.sixaxis[npad.handheld.common.last_entry_index].accelerometer = accel1;
npad.handheld.sixaxis[npad.handheld.common.last_entry_index].gyroscope = gyro1;
}
break;
case NPadControllerType::JoyDual:
@@ -433,15 +434,21 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8*
dual_entry.pad.l_stick = pad_state.l_stick;
dual_entry.pad.r_stick = pad_state.r_stick;
if (sixaxis_sensor_enabled && motion_sensors[i]) {
// TODO(anirudhb): Separate inputs for dual joy-cons.
npad.left_dual.sixaxis[npad.left_dual.common.last_entry_index].accelerometer =
accel;
npad.left_dual.sixaxis[npad.left_dual.common.last_entry_index].gyroscope = gyro;
npad.right_dual.sixaxis[npad.right_dual.common.last_entry_index].accelerometer =
accel;
npad.right_dual.sixaxis[npad.right_dual.common.last_entry_index].gyroscope = gyro;
if (sixaxis_sensor_enabled) {
if (motion_sensors[i][0] && motion_sensors[i][1]) {
npad.left_dual.sixaxis[npad.left_dual.common.last_entry_index].accelerometer =
accel1;
npad.left_dual.sixaxis[npad.left_dual.common.last_entry_index].gyroscope = gyro1;
npad.right_dual.sixaxis[npad.right_dual.common.last_entry_index].accelerometer =
accel2;
npad.right_dual.sixaxis[npad.right_dual.common.last_entry_index].gyroscope =
gyro2;
} else {
npad.right_dual.sixaxis[npad.right_dual.common.last_entry_index].accelerometer =
accel1;
npad.right_dual.sixaxis[npad.right_dual.common.last_entry_index].gyroscope =
gyro1;
}
}
break;
case NPadControllerType::JoyLeft:
@@ -452,9 +459,9 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8*
left_entry.pad.l_stick = pad_state.l_stick;
left_entry.pad.r_stick = pad_state.r_stick;
if (sixaxis_sensor_enabled && motion_sensors[i]) {
npad.left.sixaxis[npad.left.common.last_entry_index].accelerometer = accel;
npad.left.sixaxis[npad.left.common.last_entry_index].gyroscope = gyro;
if (sixaxis_sensor_enabled && motion_sensors[i][0]) {
npad.left.sixaxis[npad.left.common.last_entry_index].accelerometer = accel1;
npad.left.sixaxis[npad.left.common.last_entry_index].gyroscope = gyro1;
}
break;
case NPadControllerType::JoyRight:
@@ -465,9 +472,9 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8*
right_entry.pad.l_stick = pad_state.l_stick;
right_entry.pad.r_stick = pad_state.r_stick;
if (sixaxis_sensor_enabled && motion_sensors[i]) {
npad.right.sixaxis[npad.right.common.last_entry_index].accelerometer = accel;
npad.right.sixaxis[npad.right.common.last_entry_index].gyroscope = gyro;
if (sixaxis_sensor_enabled && motion_sensors[i][0]) {
npad.right.sixaxis[npad.right.common.last_entry_index].accelerometer = accel1;
npad.right.sixaxis[npad.right.common.last_entry_index].gyroscope = gyro1;
}
break;
case NPadControllerType::Pokeball:
@@ -489,9 +496,9 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8*
main_controller.pad.l_stick = pad_state.l_stick;
main_controller.pad.r_stick = pad_state.r_stick;
if (sixaxis_sensor_enabled && motion_sensors[i]) {
npad.full.sixaxis[npad.full.common.last_entry_index].accelerometer = accel;
npad.full.sixaxis[npad.full.common.last_entry_index].gyroscope = gyro;
if (sixaxis_sensor_enabled && motion_sensors[i][0]) {
npad.full.sixaxis[npad.full.common.last_entry_index].accelerometer = accel1;
npad.full.sixaxis[npad.full.common.last_entry_index].gyroscope = gyro1;
}
break;
}

View File

@@ -340,7 +340,7 @@ private:
std::array<std::unique_ptr<Input::AnalogDevice>, Settings::NativeAnalog::NUM_STICKS_HID>,
10>
sticks;
std::array<std::unique_ptr<Input::MotionDevice>, 10> motion_sensors;
std::array<std::array<std::unique_ptr<Input::MotionDevice>, 2>, 10> motion_sensors;
bool sixaxis_sensor_enabled{true};
std::vector<u32> supported_npad_id_types{};
NpadHoldType hold_type{NpadHoldType::Vertical};

View File

@@ -324,12 +324,19 @@ enum class ControllerType {
LeftJoycon,
};
struct MotionRaw {
bool enabled;
std::string device;
};
struct PlayerInput {
bool connected;
ControllerType type;
ButtonsRaw buttons;
AnalogsRaw analogs;
std::array<MotionRaw, 2> motion_devices;
u32 body_color_right;
u32 button_color_right;
u32 body_color_left;

View File

@@ -52,6 +52,17 @@ MotionEmu* GetMotionEmu() {
return motion_emu.get();
}
std::string GenerateMotionParam(const std::string& address, int port, int pad_index, float cx,
float cy, float cz, float sensitivity) {
Common::ParamPackage param{
{"engine", "cemuhookudp"}, {"address", address},
{"port", std::to_string(port)}, {"pad_index", std::to_string(pad_index)},
{"cx", std::to_string(cx)}, {"cy", std::to_string(cy)},
{"cz", std::to_string(cz)}, {"sensitivity", std::to_string(sensitivity)},
};
return param.Serialize();
}
std::string GenerateKeyboardParam(int key_code) {
Common::ParamPackage param{
{"engine", "keyboard"},

View File

@@ -30,6 +30,10 @@ class MotionEmu;
/// Gets the motion emulation factory.
MotionEmu* GetMotionEmu();
/// Generates a serialized param package for creating a cemuhookudp motion device
std::string GenerateMotionParam(const std::string& address, int port, int pad_index, float cx,
float cy, float cz, float sensitivity);
/// Generates a serialized param package for creating a keyboard button device
std::string GenerateKeyboardParam(int key_code);

View File

@@ -28,14 +28,24 @@ private:
class UDPMotionDevice final : public Input::MotionDevice {
public:
explicit UDPMotionDevice(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {}
explicit UDPMotionDevice(const std::string& host, int port, int pad_index, Common::Vec3f offset,
float sensitivity)
: offset(offset), sensitivity(sensitivity) {
status = std::make_shared<DeviceStatus>();
client = std::make_unique<Client>(status, host, port, pad_index);
}
std::tuple<Common::Vec3<float>, Common::Vec3<float>> GetStatus() const override {
std::lock_guard guard(status->update_mutex);
return status->motion_status;
std::tuple<Common::Vec3<float>, Common::Vec3<float>> motion = status->motion_status;
std::get<0>(motion) = (std::get<0>(motion) + offset) * sensitivity;
return motion;
}
private:
std::shared_ptr<DeviceStatus> status;
std::unique_ptr<Client> client;
Common::Vec3f offset;
float sensitivity;
};
class UDPTouchFactory final : public Input::Factory<Input::TouchDevice> {
@@ -73,14 +83,13 @@ public:
std::string host = params.Get("host", "127.0.0.1");
int port = params.Get("port", 26760);
int pad_index = params.Get("pad_index", 0);
status = std::make_shared<DeviceStatus>();
client = std::make_shared<Client>(status, host, port, pad_index);
return std::make_unique<UDPMotionDevice>(status);
float cx = params.Get("cx", 0);
float cy = params.Get("cy", 0);
float cz = params.Get("cz", 0);
Common::Vec3f offset(cx, cy, cz);
float sensitivity = params.Get("sensitivity", 1);
return std::make_unique<UDPMotionDevice>(host, port, pad_index, offset, sensitivity);
}
private:
std::shared_ptr<DeviceStatus> status;
std::shared_ptr<Client> client;
};
State::State() {

View File

@@ -293,6 +293,26 @@ void Config::ReadPlayerValues() {
player_analogs = default_param;
}
}
for (int i = 0; i < 2; ++i) {
const std::string default_param =
InputCommon::GenerateMotionParam("127.0.0.1", 26760, 0, 0, 0, 0, 1);
auto& player_motion = player.motion_devices[i];
player_motion.device =
qt_config
->value(QStringLiteral("player_%1_motion_device%2").arg(p).arg(i),
QString::fromStdString(default_param))
.toString()
.toStdString();
player_motion.enabled =
qt_config
->value(QStringLiteral("player_%1_motion_device%2/enabled").arg(p).arg(i), false)
.toBool();
if (player_motion.device.empty()) {
player_motion.device = default_param;
}
}
}
std::stable_partition(
@@ -845,6 +865,16 @@ void Config::SavePlayerValues() {
QString::fromStdString(player.analogs[i]),
QString::fromStdString(default_param));
}
for (int i = 0; i < 2; ++i) {
const std::string default_param =
InputCommon::GenerateMotionParam("127.0.0.1", 26760, 0, 0, 0, 0, 1);
WriteSetting(QStringLiteral("player_%1_motion_device%2").arg(p).arg(i),
QString::fromStdString(player.motion_devices[i].device),
QString::fromStdString(default_param));
WriteSetting(QStringLiteral("player_%1_motion_device%2/enabled").arg(p).arg(i),
player.motion_devices[i].enabled);
}
}
}

View File

@@ -10,9 +10,13 @@
#include <QKeyEvent>
#include <QMenu>
#include <QMessageBox>
#include <QProgressBar>
#include <QProgressDialog>
#include <QThread>
#include <QTimer>
#include "common/assert.h"
#include "common/param_package.h"
#include "core/frontend/input.h"
#include "input_common/main.h"
#include "ui_configure_input_player.h"
#include "yuzu/configuration/config.h"
@@ -176,8 +180,12 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
if (debug)
layout = Settings::ControllerType::DualJoycon;
ui->firstMotionDeviceInner->hide();
ui->secondMotionDeviceInner->hide();
switch (layout) {
case Settings::ControllerType::ProController:
ui->secondMotionDevice_2->hide(); // frame
case Settings::ControllerType::DualJoycon:
layout_hidden = {
ui->buttonSL,
@@ -187,6 +195,7 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
};
break;
case Settings::ControllerType::LeftJoycon:
ui->secondMotionDevice_2->hide(); // frame
layout_hidden = {
ui->right_body_button,
ui->right_buttons_button,
@@ -205,6 +214,7 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
};
break;
case Settings::ControllerType::RightJoycon:
ui->secondMotionDevice_2->hide(); // frame
layout_hidden = {
ui->left_body_button, ui->left_buttons_button,
ui->left_body_label, ui->left_buttons_label,
@@ -217,6 +227,10 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
break;
}
if (layout == Settings::ControllerType::DualJoycon) {
ui->firstMotionEnabled->setText(tr("Left"));
}
if (debug || layout == Settings::ControllerType::ProController) {
ui->controller_color->hide();
} else {
@@ -234,6 +248,59 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
for (auto* widget : layout_hidden)
widget->setVisible(false);
motion_groups[0] = {
ui->motionAddress1, ui->motionPort1,
ui->motionPadIndex1, ui->gyroXoff1,
ui->gyroYoff1, ui->gyroZoff1,
ui->gyroSensitivity1, ui->calibrate1,
ui->firstMotionEnabled, ui->firstMotionDeviceInner,
};
motion_groups[1] = {
ui->motionAddress2, ui->motionPort2,
ui->motionPadIndex2, ui->gyroXoff2,
ui->gyroYoff2, ui->gyroZoff2,
ui->gyroSensitivity2, ui->calibrate2,
ui->secondMotionEnabled, ui->secondMotionDeviceInner,
};
for (const auto& group : motion_groups) {
connect(group.calibrateButton, &QPushButton::clicked, [=] {
if (QMessageBox::information(this, tr("Gyro calibration"),
tr("Put your controller flat, then press OK to continue."),
QMessageBox::Ok, QMessageBox::Cancel) == QMessageBox::Ok) {
QProgressDialog* progress = new QProgressDialog(this);
QProgressBar* bar = new QProgressBar(progress);
bar->setRange(0, 0);
bar->setValue(0);
progress->setWindowTitle(tr("Gyro calibration"));
progress->setLabelText(tr("Please wait..."));
progress->setBar(bar);
progress->setCancelButton(nullptr);
progress->show();
// Get 100 samples and set boxes
std::string address = group.address->text().toStdString();
// offsets are applied before scaling
std::string param = InputCommon::GenerateMotionParam(
address, group.port->value(), group.padIndex->value(), 0, 0, 0, 1);
std::unique_ptr<Input::MotionDevice> device =
Input::CreateDevice<Input::MotionDevice>(param);
Common::Vec3f acc;
for (int i = 0; i < 100; i++) {
Common::Vec3f gyro, accel;
std::tie(gyro, accel) = device->GetStatus();
acc += gyro;
QApplication::processEvents();
QThread::msleep(16);
}
acc /= 100;
progress->close();
group.cx->setValue(acc.x);
group.cy->setValue(acc.y);
group.cz->setValue(acc.z);
}
});
}
analog_map_stick = {ui->buttonLStickAnalog, ui->buttonRStickAnalog};
analog_map_deadzone_and_modifier_slider = {ui->sliderLStickDeadzoneAndModifier,
ui->sliderRStickDeadzoneAndModifier};
@@ -248,24 +315,25 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
button->setContextMenuPolicy(Qt::CustomContextMenu);
connect(button, &QPushButton::clicked, [=] {
HandleClick(button_map[button_id],
[=](Common::ParamPackage params) {
// Workaround for ZL & ZR for analog triggers like on XBOX controllors.
// Analog triggers (from controllers like the XBOX controller) would not
// work due to a different range of their signals (from 0 to 255 on
// analog triggers instead of -32768 to 32768 on analog joysticks). The
// SDL driver misinterprets analog triggers as analog joysticks.
// TODO: reinterpret the signal range for analog triggers to map the
// values correctly. This is required for the correct emulation of the
// analog triggers of the GameCube controller.
if (button_id == Settings::NativeButton::ZL ||
button_id == Settings::NativeButton::ZR) {
params.Set("direction", "+");
params.Set("threshold", "0.5");
}
buttons_param[button_id] = std::move(params);
},
InputCommon::Polling::DeviceType::Button);
HandleClick(
button_map[button_id],
[=](Common::ParamPackage params) {
// Workaround for ZL & ZR for analog triggers like on XBOX controllors.
// Analog triggers (from controllers like the XBOX controller) would not
// work due to a different range of their signals (from 0 to 255 on
// analog triggers instead of -32768 to 32768 on analog joysticks). The
// SDL driver misinterprets analog triggers as analog joysticks.
// TODO: reinterpret the signal range for analog triggers to map the
// values correctly. This is required for the correct emulation of the
// analog triggers of the GameCube controller.
if (button_id == Settings::NativeButton::ZL ||
button_id == Settings::NativeButton::ZR) {
params.Set("direction", "+");
params.Set("threshold", "0.5");
}
buttons_param[button_id] = std::move(params);
},
InputCommon::Polling::DeviceType::Button);
});
connect(button, &QPushButton::customContextMenuRequested, [=](const QPoint& menu_location) {
QMenu context_menu;
@@ -291,12 +359,13 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
analog_button->setContextMenuPolicy(Qt::CustomContextMenu);
connect(analog_button, &QPushButton::clicked, [=]() {
HandleClick(analog_map_buttons[analog_id][sub_button_id],
[=](const Common::ParamPackage& params) {
SetAnalogButton(params, analogs_param[analog_id],
analog_sub_buttons[sub_button_id]);
},
InputCommon::Polling::DeviceType::Button);
HandleClick(
analog_map_buttons[analog_id][sub_button_id],
[=](const Common::ParamPackage& params) {
SetAnalogButton(params, analogs_param[analog_id],
analog_sub_buttons[sub_button_id]);
},
InputCommon::Polling::DeviceType::Button);
});
connect(analog_button, &QPushButton::customContextMenuRequested,
[=](const QPoint& menu_location) {
@@ -387,11 +456,16 @@ void ConfigureInputPlayer::ApplyConfiguration() {
debug ? Settings::values.debug_pad_buttons : Settings::values.players[player_index].buttons;
auto& analogs =
debug ? Settings::values.debug_pad_analogs : Settings::values.players[player_index].analogs;
auto& motion = Settings::values.players[player_index].motion_devices;
std::transform(buttons_param.begin(), buttons_param.end(), buttons.begin(),
[](const Common::ParamPackage& param) { return param.Serialize(); });
std::transform(analogs_param.begin(), analogs_param.end(), analogs.begin(),
[](const Common::ParamPackage& param) { return param.Serialize(); });
std::transform(motion_param.begin(), motion_param.end(), motion.begin(),
[](const MotionParam& param) {
return Settings::MotionRaw{param.enabled, param.param.Serialize()};
});
if (debug)
return;
@@ -443,6 +517,11 @@ void ConfigureInputPlayer::LoadConfiguration() {
std::transform(Settings::values.players[player_index].analogs.begin(),
Settings::values.players[player_index].analogs.end(), analogs_param.begin(),
[](const std::string& str) { return Common::ParamPackage(str); });
std::transform(Settings::values.players[player_index].motion_devices.begin(),
Settings::values.players[player_index].motion_devices.end(),
motion_param.begin(), [](const Settings::MotionRaw& raw) {
return MotionParam{raw.enabled, Common::ParamPackage(raw.device)};
});
}
UpdateButtonLabels();
@@ -480,6 +559,17 @@ void ConfigureInputPlayer::RestoreDefaults() {
SetAnalogButton(params, analogs_param[analog_id], analog_sub_buttons[sub_button_id]);
}
}
for (const auto& group : motion_groups) {
group.address->setText(tr("127.0.0.1"));
group.port->setValue(26760);
group.padIndex->setValue(0);
group.cx->setValue(0);
group.cy->setValue(0);
group.cz->setValue(0);
group.enabled->setChecked(false);
group.inner->hide();
}
UpdateButtonLabels();
}
@@ -553,6 +643,20 @@ void ConfigureInputPlayer::UpdateButtonLabels() {
}
}
}
for (int motion_id = 0; motion_id < 2; motion_id++) {
auto& param = motion_param[motion_id];
auto& group = motion_groups[motion_id];
group.address->setText(QString::fromStdString(param.param.Get("address", "127.0.0.1")));
group.port->setValue(param.param.Get("port", 26760));
group.padIndex->setValue(param.param.Get("pad_index", 0));
group.cx->setValue(param.param.Get("cx", 0));
group.cy->setValue(param.param.Get("cy", 0));
group.cz->setValue(param.param.Get("cz", 0));
group.enabled->setChecked(param.enabled);
group.inner->setVisible(param.enabled);
}
}
void ConfigureInputPlayer::HandleClick(

View File

@@ -17,6 +17,9 @@
#include "ui_configure_input.h"
class QKeyEvent;
class QLineEdit;
class QSpinBox;
class QDoubleSpinBox;
class QPushButton;
class QString;
class QTimer;
@@ -78,8 +81,14 @@ private:
/// This will be the the setting function when an input is awaiting configuration.
std::optional<std::function<void(const Common::ParamPackage&)>> input_setter;
struct MotionParam {
bool enabled;
Common::ParamPackage param;
};
std::array<Common::ParamPackage, Settings::NativeButton::NumButtons> buttons_param;
std::array<Common::ParamPackage, Settings::NativeAnalog::NumAnalogs> analogs_param;
std::array<MotionParam, 2> motion_param;
static constexpr int ANALOG_SUB_BUTTONS_NUM = 5;
@@ -102,6 +111,21 @@ private:
std::array<QLabel*, Settings::NativeAnalog::NumAnalogs>
analog_map_deadzone_and_modifier_slider_label;
/// Motion inputs are represented with 6 fields, used to configure calibration and connection
struct MotionGroup {
QLineEdit* address;
QSpinBox* port;
QSpinBox* padIndex;
QDoubleSpinBox* cx;
QDoubleSpinBox* cy;
QDoubleSpinBox* cz;
QDoubleSpinBox* sensitivity;
QPushButton* calibrateButton;
QCheckBox* enabled;
QFrame* inner;
};
std::array<MotionGroup, 2> motion_groups;
static const std::array<std::string, ANALOG_SUB_BUTTONS_NUM> analog_sub_buttons;
std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>> device_pollers;

File diff suppressed because it is too large Load Diff