diff --git a/Source/Core/DolphinLibretro/Common/Options.cpp b/Source/Core/DolphinLibretro/Common/Options.cpp index 3810a65c461c..6c42ca0e1551 100644 --- a/Source/Core/DolphinLibretro/Common/Options.cpp +++ b/Source/Core/DolphinLibretro/Common/Options.cpp @@ -1715,6 +1715,20 @@ static struct retro_core_option_v2_definition option_defs[] = { }, "disabled" }, + { + Libretro::Options::wiimote::IR_PASSTHROUGH, + "Wiimote IR > Wiimote IR Passthrough", + "Wiimote IR Passthrough", + "Take the Wiimote camera's view of the sensor bar straight from the frontend, instead of deriving it from a cursor position. The frontend supplies up to four IR objects on pointer indices 1-4; Wiimote IR Mode, Total Yaw, Total Pitch and Vertical Offset are all bypassed. For frontends that know the real geometry (a VR room, a tracked light gun) this is exact, and it carries roll and distance, which a cursor cannot. Leave off for a mouse or a gamepad.", + nullptr, + CATEGORY_WIIMOTE, + { + { "disabled", nullptr }, + { "enabled", nullptr }, + { nullptr, nullptr } + }, + "disabled" + }, #if defined(HAS_OPENGL) && defined(__WEBOS__) { diff --git a/Source/Core/DolphinLibretro/Common/Options.h b/Source/Core/DolphinLibretro/Common/Options.h index ddd01b3ec8b3..9d15da5e99c1 100644 --- a/Source/Core/DolphinLibretro/Common/Options.h +++ b/Source/Core/DolphinLibretro/Common/Options.h @@ -302,6 +302,10 @@ namespace wiimote { constexpr const char IR_MODIFIER[] = "dolphin_ir_modifier"; constexpr const char SWING_MODIFIER[] = "dolphin_swing_modifier"; constexpr const char SWING_ANGLE[] = "dolphin_swing_angle"; + + // Raw IR: the frontend supplies the camera's view of the sensor bar directly, + // instead of the core synthesising it from a cursor position. + constexpr const char IR_PASSTHROUGH[] = "dolphin_ir_passthrough"; } // namespace wiimote // ====================================================== diff --git a/Source/Core/DolphinLibretro/Input.cpp b/Source/Core/DolphinLibretro/Input.cpp index 1da73bbc7493..ab61d23c96ff 100644 --- a/Source/Core/DolphinLibretro/Input.cpp +++ b/Source/Core/DolphinLibretro/Input.cpp @@ -38,6 +38,7 @@ #include "InputCommon/ControlReference/ExpressionParser.h" #include "InputCommon/ControllerEmu/Control/Control.h" #include "InputCommon/ControllerEmu/ControlGroup/Attachments.h" +#include "InputCommon/ControllerEmu/ControlGroup/IRPassthrough.h" #include "InputCommon/ControllerEmu/Setting/NumericSetting.h" #include "InputCommon/ControllerInterface/ControllerInterface.h" #include "InputCommon/GCAdapter.h" @@ -407,11 +408,26 @@ Device::Device(unsigned device, unsigned p) : m_device(device), m_port(p) AddButton(RETRO_DEVICE_ID_MOUSE_BUTTON_5, "Button5"); return; case RETRO_DEVICE_POINTER: - AddButton(RETRO_DEVICE_ID_POINTER_PRESSED, "Pressed0", 0); - AddAxis(RETRO_DEVICE_ID_POINTER_X, -0x8000, "X0-", 0); - AddAxis(RETRO_DEVICE_ID_POINTER_X, 0x7FFF, "X0+", 0); - AddAxis(RETRO_DEVICE_ID_POINTER_Y, -0x8000, "Y0-", 0); - AddAxis(RETRO_DEVICE_ID_POINTER_Y, 0x7FFF, "Y0+", 0); + // All four touch indices, not just the first. libretro's pointer is already + // multi-touch, and IR passthrough needs four independent points — one per + // object the Wiimote's camera can see — so the indices carry them rather + // than inventing a device type for it. Index 0 keeps the names it had, so + // every existing binding (the IR cursor in mouse mode) is untouched. + { + static const char* const kPressed[] = { "Pressed0", "Pressed1", "Pressed2", "Pressed3" }; + static const char* const kXNeg[] = { "X0-", "X1-", "X2-", "X3-" }; + static const char* const kXPos[] = { "X0+", "X1+", "X2+", "X3+" }; + static const char* const kYNeg[] = { "Y0-", "Y1-", "Y2-", "Y3-" }; + static const char* const kYPos[] = { "Y0+", "Y1+", "Y2+", "Y3+" }; + for (unsigned i = 0; i < 4; ++i) + { + AddButton(RETRO_DEVICE_ID_POINTER_PRESSED, kPressed[i], i); + AddAxis(RETRO_DEVICE_ID_POINTER_X, -0x8000, kXNeg[i], i); + AddAxis(RETRO_DEVICE_ID_POINTER_X, 0x7FFF, kXPos[i], i); + AddAxis(RETRO_DEVICE_ID_POINTER_Y, -0x8000, kYNeg[i], i); + AddAxis(RETRO_DEVICE_ID_POINTER_Y, 0x7FFF, kYPos[i], i); + } + } return; case RETRO_DEVICE_KEYBOARD: return; @@ -946,6 +962,53 @@ void UpdateWiimoteMappings(const WiimoteUpdateFlags& f, unsigned port, unsigned } } + // Raw IR. When this is on, the frontend hands us the camera's actual view of + // the sensor bar and BuildDesiredWiimoteState uses it verbatim — the Point + // group, Total Yaw/Pitch, the vertical offset and the sensor-bar position are + // all bypassed (WiimoteEmu.cpp, "if m_ir_passthrough->enabled"). + // + // That is the whole point. The cursor path parks a notional remote two metres + // from the bar and rotates it by a scale nobody can derive, so where the game + // draws its hand depends on a constant fitted per game. A frontend that knows + // the real geometry can compute the dots outright, and gets roll and distance + // for free — neither of which two angles can express. + // + // Objects arrive on pointer indices 0-3: X and Y over the camera's 0..1 field + // (so the frontend sends the POSITIVE half of the pointer range, 0..32767), + // and PRESSED says the object is visible. Size is a small constant rather than + // a channel of its own — nothing here has a fifth axis to spare, and games + // read it to reject noise rather than to measure anything. + if (f.irPassthrough) + { + auto* wmIRPass = static_cast( + wm->GetWiimoteGroup(WiimoteEmu::WiimoteGroup::IRPassthrough)); + const bool passthrough = + Libretro::Options::GetCached(Libretro::Options::wiimote::IR_PASSTHROUGH); + + if (wmIRPass) + { + // Its own copy: the one above is scoped to the cursor-mode branch, which + // this path deliberately does not run through. + const std::string devPointer = + Libretro::Input::GetQualifiedName(port, RETRO_DEVICE_POINTER); + wmIRPass->enabled.SetValue(passthrough); + static const char* const kObj[] = { "0", "1", "2", "3" }; + for (int i = 0; i < 4; ++i) + { + // Cleared rather than left bound when off: AreInputsBound() is half of + // what selects this path, so a stale binding would keep the cursor route + // switched off after the option was turned back off. + const std::string idx = kObj[i]; + wmIRPass->SetControlExpression(i * 3 + 0, + passthrough ? "`" + devPointer + ":X" + idx + "+`" : ""); + wmIRPass->SetControlExpression(i * 3 + 1, + passthrough ? "`" + devPointer + ":Y" + idx + "+`" : ""); + wmIRPass->SetControlExpression(i * 3 + 2, + passthrough ? "`" + devPointer + ":Pressed" + idx + "` * 0.2" : ""); + } + } + } + if (f.swingAngle) { ControllerEmu::ControlGroup* wmSwing = wm->GetWiimoteGroup(WiimoteEmu::WiimoteGroup::Swing); @@ -1516,6 +1579,11 @@ void retro_set_controller_port_device_wii(unsigned port, unsigned device) f.irModifier = true; f.swingModifier = true; f.sideways = true; + // Raised at setup so the option takes effect on a cold boot. Without it the + // binding would only ever happen if the value CHANGED while running, which + // is the trap the IR offset/yaw/pitch settings already sit in — their + // declared defaults never reach the emulated remote at all. + f.irPassthrough = true; Libretro::Input::UpdateWiimoteMappings(f, port, device); wmShake->SetControlExpression(0, bindMouse("L2", devMouse + ":Middle")); // Wiimote shake X diff --git a/Source/Core/DolphinLibretro/Input.h b/Source/Core/DolphinLibretro/Input.h index 895d209ba7ea..e2d3287d1e84 100644 --- a/Source/Core/DolphinLibretro/Input.h +++ b/Source/Core/DolphinLibretro/Input.h @@ -19,11 +19,12 @@ struct WiimoteUpdateFlags bool sideways = false; bool rumble = false; bool gcMicBtn = false; + bool irPassthrough = false; bool any() const { return irMode || irOffset || irYaw || irPitch || irDeadzone || irModifier || swingModifier || - swingAngle || sideways || rumble || gcMicBtn; + swingAngle || sideways || rumble || gcMicBtn || irPassthrough; } }; diff --git a/Source/Core/DolphinLibretro/Main.cpp b/Source/Core/DolphinLibretro/Main.cpp index f7ba162441f3..bc61f5ec0c95 100644 --- a/Source/Core/DolphinLibretro/Main.cpp +++ b/Source/Core/DolphinLibretro/Main.cpp @@ -389,6 +389,7 @@ void retro_run(void) flags.swingModifier = Libretro::Options::IsUpdated(Libretro::Options::wiimote::SWING_MODIFIER); flags.swingAngle = Libretro::Options::IsUpdated(Libretro::Options::wiimote::SWING_ANGLE); flags.sideways = Libretro::Options::IsUpdated(Libretro::Options::wiimote::HOTKEY_SIDEWAYS_TOGGLE); + flags.irPassthrough = Libretro::Options::IsUpdated(Libretro::Options::wiimote::IR_PASSTHROUGH); } flags.rumble = Libretro::Options::IsUpdated(Libretro::Options::sysconf::ENABLE_RUMBLE);