diff --git a/Source/Core/DolphinLibretro/Input.cpp b/Source/Core/DolphinLibretro/Input.cpp index 1da73bbc7493..47058e70dc7b 100644 --- a/Source/Core/DolphinLibretro/Input.cpp +++ b/Source/Core/DolphinLibretro/Input.cpp @@ -52,6 +52,38 @@ #define RETRO_DEVICE_WIIMOTE_CC_PRO ((5 << 8) | RETRO_DEVICE_JOYPAD) #define RETRO_DEVICE_GC_ON_WII ((6 << 8) | RETRO_DEVICE_JOYPAD) #define RETRO_DEVICE_REAL_WIIMOTE ((6 << 8) | RETRO_DEVICE_NONE) +#define RETRO_DEVICE_WIIMOTE_MP ((7 << 8) | RETRO_DEVICE_JOYPAD) +#define RETRO_DEVICE_WIIMOTE_MP_SW ((8 << 8) | RETRO_DEVICE_JOYPAD) +#define RETRO_DEVICE_WIIMOTE_MP_NC ((9 << 8) | RETRO_DEVICE_JOYPAD) +#define RETRO_DEVICE_WIIMOTE_MP_CC ((10 << 8) | RETRO_DEVICE_JOYPAD) +#define RETRO_DEVICE_WIIMOTE_MP_CC_PRO ((11 << 8) | RETRO_DEVICE_JOYPAD) + +/// The same remote with the dongle taken off, so everything downstream stays +/// written against the five original ids. +static inline unsigned wiimote_base_device(unsigned device) +{ + switch (device) + { + case RETRO_DEVICE_WIIMOTE_MP: + return RETRO_DEVICE_WIIMOTE; + case RETRO_DEVICE_WIIMOTE_MP_SW: + return RETRO_DEVICE_WIIMOTE_SW; + case RETRO_DEVICE_WIIMOTE_MP_NC: + return RETRO_DEVICE_WIIMOTE_NC; + case RETRO_DEVICE_WIIMOTE_MP_CC: + return RETRO_DEVICE_WIIMOTE_CC; + case RETRO_DEVICE_WIIMOTE_MP_CC_PRO: + return RETRO_DEVICE_WIIMOTE_CC_PRO; + default: + return device; + } +} + +/// True when this device id carries a MotionPlus dongle. +static inline bool wiimote_has_motion_plus(unsigned device) +{ + return wiimote_base_device(device) != device; +} typedef enum { SENSOR_ACCELEROMETER = 0, @@ -77,7 +109,8 @@ static bool sensor_enabled[NUM_CONTROLLERS_FOR_SENSORS][SENSOR_COUNT] = {}; static int port_max; double g_accel_pos[NUM_CONTROLLERS_FOR_SENSORS][3] = {}; // x, y, z double g_accel_neg[NUM_CONTROLLERS_FOR_SENSORS][3] = {}; // x, y, z -double g_gyro[NUM_CONTROLLERS_FOR_SENSORS][3] = {}; +double g_gyro_pos[NUM_CONTROLLERS_FOR_SENSORS][3] = {}; // x, y, z +double g_gyro_neg[NUM_CONTROLLERS_FOR_SENSORS][3] = {}; // x, y, z static struct retro_input_descriptor descGC[] = { {0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT, "Left"}, @@ -529,6 +562,11 @@ void InitStage2() {"WiiMote + Nunchuk", RETRO_DEVICE_WIIMOTE_NC}, {"WiiMote + Classic Controller", RETRO_DEVICE_WIIMOTE_CC}, {"WiiMote + Classic Controller Pro", RETRO_DEVICE_WIIMOTE_CC_PRO}, + {"WiiMote + MotionPlus", RETRO_DEVICE_WIIMOTE_MP}, + {"WiiMote + MotionPlus (sideways)", RETRO_DEVICE_WIIMOTE_MP_SW}, + {"WiiMote + MotionPlus + Nunchuk", RETRO_DEVICE_WIIMOTE_MP_NC}, + {"WiiMote + MotionPlus + Classic Controller", RETRO_DEVICE_WIIMOTE_MP_CC}, + {"WiiMote + MotionPlus + Classic Controller Pro", RETRO_DEVICE_WIIMOTE_MP_CC_PRO}, {"Real WiiMote", RETRO_DEVICE_REAL_WIIMOTE}, }; @@ -555,6 +593,11 @@ void InitStage2() {"WiiMote + Nunchuk", RETRO_DEVICE_WIIMOTE_NC}, {"WiiMote + Classic Controller", RETRO_DEVICE_WIIMOTE_CC}, {"WiiMote + Classic Controller Pro", RETRO_DEVICE_WIIMOTE_CC_PRO}, + {"WiiMote + MotionPlus", RETRO_DEVICE_WIIMOTE_MP}, + {"WiiMote + MotionPlus (sideways)", RETRO_DEVICE_WIIMOTE_MP_SW}, + {"WiiMote + MotionPlus + Nunchuk", RETRO_DEVICE_WIIMOTE_MP_NC}, + {"WiiMote + MotionPlus + Classic Controller", RETRO_DEVICE_WIIMOTE_MP_CC}, + {"WiiMote + MotionPlus + Classic Controller Pro", RETRO_DEVICE_WIIMOTE_MP_CC_PRO}, {"Real WiiMote", RETRO_DEVICE_REAL_WIIMOTE}, {"GameCube Controller", RETRO_DEVICE_GC_ON_WII}, }; @@ -643,11 +686,12 @@ void Shutdown() { Pad::ResetRumble(i); - if(sensor_enabled[i][SENSOR_ACCELEROMETER]) - sensor_interface.set_sensor_state(0, RETRO_SENSOR_GYROSCOPE_DISABLE, 0); + // Was crossed over and hardcoded to port 0, leaving sensors running. + if (sensor_enabled[i][SENSOR_ACCELEROMETER]) + sensor_interface.set_sensor_state(i, RETRO_SENSOR_ACCELEROMETER_DISABLE, 0); - if(sensor_enabled[i][SENSOR_GYRO]) - sensor_interface.set_sensor_state(0, RETRO_SENSOR_ACCELEROMETER_DISABLE, 0); + if (sensor_enabled[i][SENSOR_GYRO]) + sensor_interface.set_sensor_state(i, RETRO_SENSOR_GYROSCOPE_DISABLE, 0); sensor_enabled[i][SENSOR_ACCELEROMETER] = false; sensor_enabled[i][SENSOR_GYRO] = false; @@ -672,7 +716,8 @@ void UpdateAccelerometer(unsigned port) float ay = sensor_interface.get_sensor_input(port, RETRO_SENSOR_ACCELEROMETER_Y) * G; float az = sensor_interface.get_sensor_input(port, RETRO_SENSOR_ACCELEROMETER_Z) * G; - if (input_types[port] == RETRO_DEVICE_WIIMOTE_SW) + // Collapsed, so a sideways remote with the dongle fitted still rotates. + if (wiimote_base_device(input_types[port]) == RETRO_DEVICE_WIIMOTE_SW) { float rx = -ay; // rotate 90° clockwise float ry = ax; @@ -696,11 +741,12 @@ void UpdateGyro(unsigned port) if (!sensor_enabled[port][SENSOR_GYRO] || !sensor_interface.get_sensor_input) return; + // rad/s about the remote's own axes: +X left, +Y back, +Z up. float gx = sensor_interface.get_sensor_input(port, RETRO_SENSOR_GYROSCOPE_X); float gy = sensor_interface.get_sensor_input(port, RETRO_SENSOR_GYROSCOPE_Y); float gz = sensor_interface.get_sensor_input(port, RETRO_SENSOR_GYROSCOPE_Z); - if (input_types[port] == RETRO_DEVICE_WIIMOTE_SW) + if (wiimote_base_device(input_types[port]) == RETRO_DEVICE_WIIMOTE_SW) { float rx = -gy; // rotate 90° clockwise float ry = gx; @@ -708,9 +754,16 @@ void UpdateGyro(unsigned port) gy = ry; } - g_gyro[port][0] = gx; - g_gyro[port][1] = gy; - g_gyro[port][2] = gz; + // Split across a one-sided pair; the expression parser clamps negatives away + // (see SensorDevice::RegisterAll). + g_gyro_pos[port][0] = std::max(0.0f, gx); + g_gyro_neg[port][0] = std::max(0.0f, -gx); + + g_gyro_pos[port][1] = std::max(0.0f, gy); + g_gyro_neg[port][1] = std::max(0.0f, -gy); + + g_gyro_pos[port][2] = std::max(0.0f, gz); + g_gyro_neg[port][2] = std::max(0.0f, -gz); } void ResetControllers(const WiimoteUpdateFlags& f) @@ -814,6 +867,9 @@ static std::string GetQualifiedNameSensor(unsigned port) // can be called from retro_run, do not reset all settings because one thing changed void UpdateWiimoteMappings(const WiimoteUpdateFlags& f, unsigned port, unsigned device) { + // ResetControllers passes input_types[port] verbatim, dongle id and all. + device = wiimote_base_device(device); + if (!f.any() || device == RETRO_DEVICE_REAL_WIIMOTE || device == RETRO_DEVICE_WIIMOTE_CC || device == RETRO_DEVICE_WIIMOTE_CC_PRO) return; @@ -1073,7 +1129,8 @@ void retro_set_controller_port_device(unsigned port, unsigned device) { retro_input_descriptor* desc; - switch (Libretro::Input::input_types[i]) + // The dongle adds no buttons, so a MotionPlus id wants its twin's descriptors. + switch (wiimote_base_device(Libretro::Input::input_types[i])) { case RETRO_DEVICE_WIIMOTE_SW: desc = Libretro::Input::descWiimoteSideways; @@ -1323,6 +1380,11 @@ void retro_set_controller_port_device_wii(unsigned port, unsigned device) #endif auto& si = Core::System::GetInstance().GetSerialInterface(); + // Take the dongle off the id and remember it, so the branches below only see + // the remotes they were written for. + const bool wantMotionPlus = wiimote_has_motion_plus(device); + device = wiimote_base_device(device); + if (Wiimote::GetConfig()->ControllersNeedToBeCreated()) { WARN_LOG_FMT(COMMON, "No controllers have been created yet"); @@ -1476,23 +1538,25 @@ void retro_set_controller_port_device_wii(unsigned port, unsigned device) wmAccel->SetControlExpression(4, "`" + devSensor + ":AccelY-`"); // Forward wmAccel->SetControlExpression(5, "`" + devSensor + ":AccelY+`"); // Backward } + } - if (Libretro::Input::sensor_enabled[port][SENSOR_GYRO]) + // A sibling, not a child: nested, gyro-without-accelerometer bound neither. + if (Libretro::Input::sensor_enabled[port][SENSOR_GYRO]) + { + // Gyroscope (6 inputs: PitchUp/Down, RollLeft/Right, YawLeft/Right) + auto* wmGyro = static_cast( + wm->GetWiimoteGroup(WiimoteEmu::WiimoteGroup::IMUGyroscope)); + if (wmGyro) { - // Gyroscope (6 inputs: PitchUp/Down, RollLeft/Right, YawLeft/Right) - auto* wmGyro = static_cast( - wm->GetWiimoteGroup(WiimoteEmu::WiimoteGroup::IMUGyroscope)); - if (wmGyro) - { - // Map libretro axes to Wiimote angular axes: - // Pitch ~ rotation around X, Roll ~ rotation around Y, Yaw ~ rotation around Z - wmGyro->SetControlExpression(0, "`" + devSensor + ":GyroX`"); // Pitch Up - wmGyro->SetControlExpression(1, "`" + devSensor + ":GyroX`*-1"); // Pitch Down - wmGyro->SetControlExpression(2, "`" + devSensor + ":GyroY`*-1"); // Roll Left - wmGyro->SetControlExpression(3, "`" + devSensor + ":GyroY`"); // Roll Right - wmGyro->SetControlExpression(4, "`" + devSensor + ":GyroZ`*-1"); // Yaw Left - wmGyro->SetControlExpression(5, "`" + devSensor + ":GyroZ`"); // Yaw Right - } + // Right-hand rule about +X left, +Y back, +Z up: +X is pitch down, + // +Y rolls the top left, +Z swings the nose left. GetRawState() + // reads these as [1]-[0], [2]-[3], [4]-[5]. + wmGyro->SetControlExpression(0, "`" + devSensor + ":GyroX-`"); // Pitch Up + wmGyro->SetControlExpression(1, "`" + devSensor + ":GyroX+`"); // Pitch Down + wmGyro->SetControlExpression(2, "`" + devSensor + ":GyroY+`"); // Roll Left + wmGyro->SetControlExpression(3, "`" + devSensor + ":GyroY-`"); // Roll Right + wmGyro->SetControlExpression(4, "`" + devSensor + ":GyroZ+`"); // Yaw Left + wmGyro->SetControlExpression(5, "`" + devSensor + ":GyroZ-`"); // Yaw Right } } } @@ -1527,6 +1591,13 @@ void retro_set_controller_port_device_wii(unsigned port, unsigned device) ControllerEmu::Attachments* wmExtension = (ControllerEmu::Attachments*)wm->GetWiimoteGroup(WiimoteGroup::Attachments); + // Index 0 is "Attach MotionPlus", the only numeric setting here (the + // attachment selector is kept out of that list). Written on both paths since + // Dolphin defaults it to true. + if (!wmExtension->numeric_settings.empty()) + static_cast*>(wmExtension->numeric_settings[0].get()) + ->SetValue(wantMotionPlus); + static_cast*>(wmOptions->numeric_settings[0].get()) ->SetValue(0); // Speaker Pan [-100, 100] static_cast*>(wmOptions->numeric_settings[1].get()) diff --git a/Source/Core/DolphinLibretro/Input.h b/Source/Core/DolphinLibretro/Input.h index 895d209ba7ea..3580f7d106d6 100644 --- a/Source/Core/DolphinLibretro/Input.h +++ b/Source/Core/DolphinLibretro/Input.h @@ -37,7 +37,8 @@ namespace Input constexpr std::string_view source = "Libretro"; extern double g_accel_pos[NUM_CONTROLLERS_FOR_SENSORS][3]; extern double g_accel_neg[NUM_CONTROLLERS_FOR_SENSORS][3]; -extern double g_gyro[NUM_CONTROLLERS_FOR_SENSORS][3]; +extern double g_gyro_pos[NUM_CONTROLLERS_FOR_SENSORS][3]; +extern double g_gyro_neg[NUM_CONTROLLERS_FOR_SENSORS][3]; static retro_sensor_interface sensor_interface = {0}; @@ -78,11 +79,17 @@ class SensorDevice : public ciface::Core::Device }; public: + /// Each axis is a PAIR of one-sided inputs: ControlExpression clamps a control + /// to >= 0, so a lone signed input loses half its travel. The IMU groups + /// subtract one direction from the other, rebuilding the signed value. void RegisterAll() { - AddInput(new ScalarInput("GyroX", &Libretro::Input::g_gyro[m_port][0])); - AddInput(new ScalarInput("GyroY", &Libretro::Input::g_gyro[m_port][1])); - AddInput(new ScalarInput("GyroZ", &Libretro::Input::g_gyro[m_port][2])); + AddInput(new ScalarInput("GyroX+", &Libretro::Input::g_gyro_pos[m_port][0])); + AddInput(new ScalarInput("GyroX-", &Libretro::Input::g_gyro_neg[m_port][0])); + AddInput(new ScalarInput("GyroY+", &Libretro::Input::g_gyro_pos[m_port][1])); + AddInput(new ScalarInput("GyroY-", &Libretro::Input::g_gyro_neg[m_port][1])); + AddInput(new ScalarInput("GyroZ+", &Libretro::Input::g_gyro_pos[m_port][2])); + AddInput(new ScalarInput("GyroZ-", &Libretro::Input::g_gyro_neg[m_port][2])); AddInput(new ScalarInput("AccelX+", &Libretro::Input::g_accel_pos[m_port][0])); AddInput(new ScalarInput("AccelX-", &Libretro::Input::g_accel_neg[m_port][0])); AddInput(new ScalarInput("AccelY+", &Libretro::Input::g_accel_pos[m_port][1])); @@ -132,7 +139,7 @@ class GyroDevice : public ciface::Core::Device }; public: - GyroDevice(unsigned port) : m_port(port) + GyroDevice(unsigned port) { AddInput(new GyroAxis(port, GyroAxis::PITCH, "Pitch")); AddInput(new GyroAxis(port, GyroAxis::ROLL, "Roll")); @@ -141,7 +148,4 @@ class GyroDevice : public ciface::Core::Device std::string GetName() const override { return "Gyroscope"; } std::string GetSource() const override { return std::string(Libretro::Input::source); } - -private: - unsigned m_port; };