Merge pull request #8575 from jordan-woyak/ciface-wiimotes

InputCommon: Add support for Wii Remotes in ControllerInterface
This commit is contained in:
Pierre Bourdon 2020-02-22 17:20:44 +01:00 committed by GitHub
commit 2b6a1ee4d8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
42 changed files with 3308 additions and 579 deletions

View File

@ -300,6 +300,12 @@ void SetBit(T& value, size_t bit_number, bool bit_value)
value &= ~(T{1} << bit_number);
}
template <size_t bit_number, typename T>
void SetBit(T& value, bool bit_value)
{
SetBit(value, bit_number, bit_value);
}
template <typename T>
class FlagBit
{
@ -340,4 +346,15 @@ public:
std::underlying_type_t<T> m_hex = 0;
};
// Left-shift a value and set new LSBs to that of the supplied LSB.
// Converts a value from a N-bit range to an (N+X)-bit range. e.g. 0x101 -> 0x10111
template <typename T>
T ExpandValue(T value, size_t left_shift_amount)
{
static_assert(std::is_unsigned<T>(), "ExpandValue is only sane on unsigned types.");
return (value << left_shift_amount) |
(T(-ExtractBit<0>(value)) >> (BitSize<T>() - left_shift_amount));
}
} // namespace Common

View File

@ -5,6 +5,7 @@
#pragma once
#include <algorithm>
#include <cmath>
#include <vector>
#include "Common/CommonTypes.h"
@ -93,6 +94,45 @@ struct Rectangle
}
};
template <typename T>
class RunningMean
{
public:
constexpr void Clear() { *this = {}; }
constexpr void Push(T x) { m_mean = m_mean + (x - m_mean) / ++m_count; }
constexpr size_t Count() const { return m_count; }
constexpr T Mean() const { return m_mean; }
private:
size_t m_count = 0;
T m_mean{};
};
template <typename T>
class RunningVariance
{
public:
constexpr void Clear() { *this = {}; }
constexpr void Push(T x)
{
const auto old_mean = m_running_mean.Mean();
m_running_mean.Push(x);
m_variance += (x - old_mean) * (x - m_running_mean.Mean());
}
constexpr size_t Count() const { return m_running_mean.Count(); }
constexpr T Mean() const { return m_running_mean.Mean(); }
constexpr T Variance() const { return m_variance / (Count() - 1); }
constexpr T StandardDeviation() const { return std::sqrt(Variance()); }
private:
RunningMean<T> m_running_mean;
T m_variance{};
};
} // namespace MathUtil
float MathFloatVectorSum(const std::vector<float>&);

View File

@ -20,6 +20,11 @@ union TVec3
TVec3() = default;
TVec3(T _x, T _y, T _z) : data{_x, _y, _z} {}
template <typename OtherT>
explicit TVec3(const TVec3<OtherT>& other) : TVec3(other.x, other.y, other.z)
{
}
TVec3 Cross(const TVec3& rhs) const
{
return {(y * rhs.z) - (rhs.y * z), (z * rhs.x) - (rhs.z * x), (x * rhs.y) - (rhs.x * y)};
@ -98,6 +103,11 @@ TVec3<bool> operator<(const TVec3<T>& lhs, const TVec3<T>& rhs)
return lhs.Map(std::less<T>{}, rhs);
}
inline TVec3<bool> operator!(const TVec3<bool>& vec)
{
return {!vec.x, !vec.y, !vec.z};
}
template <typename T>
auto operator+(const TVec3<T>& lhs, const TVec3<T>& rhs) -> TVec3<decltype(lhs.x + rhs.x)>
{
@ -197,6 +207,11 @@ union TVec2
TVec2() = default;
TVec2(T _x, T _y) : data{_x, _y} {}
template <typename OtherT>
explicit TVec2(const TVec2<OtherT>& other) : TVec2(other.x, other.y)
{
}
T Cross(const TVec2& rhs) const { return (x * rhs.y) - (y * rhs.x); }
T Dot(const TVec2& rhs) const { return (x * rhs.x) + (y * rhs.y); }
T LengthSquared() const { return Dot(*this); }
@ -217,6 +232,20 @@ union TVec2
return *this;
}
TVec2& operator*=(const TVec2& rhs)
{
x *= rhs.x;
y *= rhs.y;
return *this;
}
TVec2& operator/=(const TVec2& rhs)
{
x /= rhs.x;
y /= rhs.y;
return *this;
}
TVec2& operator*=(T scalar)
{
x *= scalar;
@ -242,6 +271,17 @@ union TVec2
};
};
template <typename T>
TVec2<bool> operator<(const TVec2<T>& lhs, const TVec2<T>& rhs)
{
return {lhs.x < rhs.x, lhs.y < rhs.y};
}
inline TVec2<bool> operator!(const TVec2<bool>& vec)
{
return {!vec.x, !vec.y};
}
template <typename T>
TVec2<T> operator+(TVec2<T> lhs, const TVec2<T>& rhs)
{
@ -255,15 +295,27 @@ TVec2<T> operator-(TVec2<T> lhs, const TVec2<T>& rhs)
}
template <typename T>
TVec2<T> operator*(TVec2<T> lhs, T scalar)
TVec2<T> operator*(TVec2<T> lhs, const TVec2<T>& rhs)
{
return lhs *= scalar;
return lhs *= rhs;
}
template <typename T>
TVec2<T> operator/(TVec2<T> lhs, T scalar)
TVec2<T> operator/(TVec2<T> lhs, const TVec2<T>& rhs)
{
return lhs /= scalar;
return lhs /= rhs;
}
template <typename T, typename T2>
auto operator*(TVec2<T> lhs, T2 scalar)
{
return TVec2<decltype(lhs.x * scalar)>(lhs) *= scalar;
}
template <typename T, typename T2>
auto operator/(TVec2<T> lhs, T2 scalar)
{
return TVec2<decltype(lhs.x / scalar)>(lhs) /= scalar;
}
using Vec2 = TVec2<float>;

View File

@ -234,6 +234,7 @@ void SConfig::SaveCoreSettings(IniFile& ini)
core->Set("WiiKeyboard", m_WiiKeyboard);
core->Set("WiimoteContinuousScanning", m_WiimoteContinuousScanning);
core->Set("WiimoteEnableSpeaker", m_WiimoteEnableSpeaker);
core->Set("WiimoteControllerInterface", connect_wiimotes_for_ciface);
core->Set("RunCompareServer", bRunCompareServer);
core->Set("RunCompareClient", bRunCompareClient);
core->Set("MMU", bMMU);
@ -511,6 +512,7 @@ void SConfig::LoadCoreSettings(IniFile& ini)
core->Get("WiiKeyboard", &m_WiiKeyboard, false);
core->Get("WiimoteContinuousScanning", &m_WiimoteContinuousScanning, false);
core->Get("WiimoteEnableSpeaker", &m_WiimoteEnableSpeaker, false);
core->Get("WiimoteControllerInterface", &connect_wiimotes_for_ciface, false);
core->Get("RunCompareServer", &bRunCompareServer, false);
core->Get("RunCompareClient", &bRunCompareClient, false);
core->Get("MMU", &bMMU, bMMU);

View File

@ -72,6 +72,7 @@ struct SConfig
bool m_WiiKeyboard;
bool m_WiimoteContinuousScanning;
bool m_WiimoteEnableSpeaker;
bool connect_wiimotes_for_ciface;
// ISO folder
std::vector<std::string> m_ISOFolder;

View File

@ -5,6 +5,7 @@
#include <cassert>
#include "Common/BitUtils.h"
#include "Common/MathUtil.h"
#include "Core/HW/WiimoteCommon/DataReport.h"
namespace WiimoteCommon
@ -75,40 +76,35 @@ struct IncludeAccel : virtual DataReportManipulator
void GetAccelData(AccelData* result) const override
{
const AccelMSB accel = Common::BitCastPtr<AccelMSB>(data_ptr + 2);
result->x = accel.x << 2;
result->y = accel.y << 2;
result->z = accel.z << 2;
// LSBs
const CoreData core = Common::BitCastPtr<CoreData>(data_ptr);
result->x |= core.acc_bits & 0b11;
result->y |= (core.acc_bits2 & 0b1) << 1;
result->z |= core.acc_bits2 & 0b10;
// X has 10 bits of precision.
result->value.x = accel.x << 2;
result->value.x |= core.acc_bits & 0b11;
// Y and Z only have 9 bits of precision. (convert them to 10)
result->value.y =
Common::ExpandValue<u16>(accel.y << 1 | Common::ExtractBit<0>(core.acc_bits2), 1);
result->value.z =
Common::ExpandValue<u16>(accel.z << 1 | Common::ExtractBit<1>(core.acc_bits2), 1);
}
void SetAccelData(const AccelData& new_accel) override
{
AccelMSB accel = {};
accel.x = new_accel.x >> 2;
accel.y = new_accel.y >> 2;
accel.z = new_accel.z >> 2;
Common::BitCastPtr<AccelMSB>(data_ptr + 2) = accel;
Common::BitCastPtr<AccelMSB>(data_ptr + 2) = AccelMSB(new_accel.value / 4);
// LSBs
CoreData core = Common::BitCastPtr<CoreData>(data_ptr);
core.acc_bits = (new_accel.x >> 0) & 0b11;
core.acc_bits2 = (new_accel.y >> 1) & 0x1;
core.acc_bits2 |= (new_accel.z & 0xb10);
core.acc_bits = (new_accel.value.x >> 0) & 0b11;
core.acc_bits2 = (new_accel.value.y >> 1) & 0x1;
core.acc_bits2 |= (new_accel.value.z & 0xb10);
Common::BitCastPtr<CoreData>(data_ptr) = core;
}
bool HasAccel() const override { return true; }
private:
struct AccelMSB
{
u8 x, y, z;
};
using AccelMSB = Common::TVec3<u8>;
static_assert(sizeof(AccelMSB) == 3, "Wrong size");
};
@ -195,26 +191,28 @@ struct ReportExt21 : NoCore, NoAccel, NoIR, IncludeExt<0, 21>
struct ReportInterleave1 : IncludeCore, IncludeIR<3, 18, 0>, NoExt
{
// FYI: Only 8-bits of precision in this report, and no Y axis.
// Only contains 4 MSB of Z axis.
void GetAccelData(AccelData* accel) const override
{
accel->x = data_ptr[2] << 2;
// X axis only has 8 bits of precision. (converted to 10)
accel->value.x = Common::ExpandValue<u16>(data_ptr[2], 2);
// Retain lower 6 bits.
accel->z &= 0b111111;
// Y axis is not contained in this report. (provided by "Interleave2")
// Clear upper bits, retain lower bits. (provided by "Interleave2")
accel->value.z &= 0b111111;
// Report only contains 4 MSB of Z axis.
const CoreData core = Common::BitCastPtr<CoreData>(data_ptr);
accel->z |= (core.acc_bits << 6) | (core.acc_bits2 << 8);
accel->value.z |= (core.acc_bits << 6) | (core.acc_bits2 << 8);
}
void SetAccelData(const AccelData& accel) override
{
data_ptr[2] = accel.x >> 2;
data_ptr[2] = accel.value.x >> 2;
CoreData core = Common::BitCastPtr<CoreData>(data_ptr);
core.acc_bits = (accel.z >> 6) & 0b11;
core.acc_bits2 = (accel.z >> 8) & 0b11;
core.acc_bits = (accel.value.z >> 6) & 0b11;
core.acc_bits2 = (accel.value.z >> 8) & 0b11;
Common::BitCastPtr<CoreData>(data_ptr) = core;
}
@ -226,26 +224,28 @@ struct ReportInterleave1 : IncludeCore, IncludeIR<3, 18, 0>, NoExt
struct ReportInterleave2 : IncludeCore, IncludeIR<3, 18, 18>, NoExt
{
// FYI: Only 8-bits of precision in this report, and no X axis.
// Only contains 4 LSB of Z axis.
void GetAccelData(AccelData* accel) const override
{
accel->y = data_ptr[2] << 2;
// X axis is not contained in this report. (provided by "Interleave1")
// Retain upper 4 bits.
accel->z &= ~0b111111;
// Y axis only has 8 bits of precision. (converted to 10)
accel->value.y = Common::ExpandValue<u16>(data_ptr[2], 2);
// Clear lower bits, retain upper bits. (provided by "Interleave1")
accel->value.z &= ~0b111111;
// Report only contains 4 LSBs of Z axis. (converted to 6)
const CoreData core = Common::BitCastPtr<CoreData>(data_ptr);
accel->z |= (core.acc_bits << 2) | (core.acc_bits2 << 4);
accel->value.z |= Common::ExpandValue<u16>(core.acc_bits | core.acc_bits2 << 2, 2);
}
void SetAccelData(const AccelData& accel) override
{
data_ptr[2] = accel.y >> 2;
data_ptr[2] = accel.value.y >> 2;
CoreData core = Common::BitCastPtr<CoreData>(data_ptr);
core.acc_bits = (accel.z >> 2) & 0b11;
core.acc_bits2 = (accel.z >> 4) & 0b11;
core.acc_bits = (accel.value.z >> 2) & 0b11;
core.acc_bits2 = (accel.value.z >> 4) & 0b11;
Common::BitCastPtr<CoreData>(data_ptr) = core;
}

View File

@ -8,9 +8,11 @@
#include <memory>
#include "Common/CommonTypes.h"
#include "Common/Matrix.h"
#include "Core/HW/WiimoteCommon/WiimoteConstants.h"
#include "Core/HW/WiimoteCommon/WiimoteHid.h"
#include "Core/HW/WiimoteCommon/WiimoteReport.h"
#include "InputCommon/ControllerEmu/ControllerEmu.h"
namespace WiimoteCommon
{
@ -21,12 +23,6 @@ class DataReportManipulator
public:
virtual ~DataReportManipulator() = default;
// Accel data handled as if there were always 10 bits of precision.
struct AccelData
{
u16 x, y, z;
};
using CoreData = ButtonData;
virtual bool HasCore() const = 0;
@ -66,7 +62,6 @@ public:
explicit DataReportBuilder(InputReportID rpt_id);
using CoreData = ButtonData;
using AccelData = DataReportManipulator::AccelData;
void SetMode(InputReportID rpt_id);
InputReportID GetMode() const;
@ -99,11 +94,10 @@ public:
u32 GetDataSize() const;
private:
static constexpr int HEADER_SIZE = 2;
static constexpr int MAX_DATA_SIZE = MAX_PAYLOAD - 2;
private:
TypedHIDInputData<std::array<u8, MAX_DATA_SIZE>> m_data;
std::unique_ptr<DataReportManipulator> m_manip;

View File

@ -10,6 +10,10 @@ namespace WiimoteCommon
{
constexpr u8 MAX_PAYLOAD = 23;
// Based on testing, old WiiLi.org docs, and WiiUse library:
// Max battery level seems to be 0xc8 (decimal 200)
constexpr u8 MAX_BATTERY_LEVEL = 0xc8;
enum class InputReportID : u8
{
Status = 0x20,

View File

@ -7,7 +7,9 @@
#include <vector>
#include "Common/CommonTypes.h"
#include "Common/Matrix.h"
#include "Core/HW/WiimoteCommon/WiimoteConstants.h"
#include "InputCommon/ControllerEmu/ControllerEmu.h"
#ifdef _MSC_VER
#pragma warning(push)
@ -41,6 +43,8 @@ static_assert(sizeof(OutputReportGeneric) == 2, "Wrong size");
struct OutputReportRumble
{
static constexpr OutputReportID REPORT_ID = OutputReportID::Rumble;
u8 rumble : 1;
};
static_assert(sizeof(OutputReportRumble) == 1, "Wrong size");
@ -55,8 +59,34 @@ struct OutputReportEnableFeature
};
static_assert(sizeof(OutputReportEnableFeature) == 1, "Wrong size");
struct OutputReportIRLogicEnable : OutputReportEnableFeature
{
static constexpr OutputReportID REPORT_ID = OutputReportID::IRLogicEnable;
};
static_assert(sizeof(OutputReportIRLogicEnable) == 1, "Wrong size");
struct OutputReportIRLogicEnable2 : OutputReportEnableFeature
{
static constexpr OutputReportID REPORT_ID = OutputReportID::IRLogicEnable2;
};
static_assert(sizeof(OutputReportIRLogicEnable2) == 1, "Wrong size");
struct OutputReportSpeakerEnable : OutputReportEnableFeature
{
static constexpr OutputReportID REPORT_ID = OutputReportID::SpeakerEnable;
};
static_assert(sizeof(OutputReportSpeakerEnable) == 1, "Wrong size");
struct OutputReportSpeakerMute : OutputReportEnableFeature
{
static constexpr OutputReportID REPORT_ID = OutputReportID::SpeakerMute;
};
static_assert(sizeof(OutputReportSpeakerMute) == 1, "Wrong size");
struct OutputReportLeds
{
static constexpr OutputReportID REPORT_ID = OutputReportID::LED;
u8 rumble : 1;
u8 ack : 1;
u8 : 2;
@ -66,6 +96,8 @@ static_assert(sizeof(OutputReportLeds) == 1, "Wrong size");
struct OutputReportMode
{
static constexpr OutputReportID REPORT_ID = OutputReportID::ReportMode;
u8 rumble : 1;
u8 ack : 1;
u8 continuous : 1;
@ -76,6 +108,8 @@ static_assert(sizeof(OutputReportMode) == 2, "Wrong size");
struct OutputReportRequestStatus
{
static constexpr OutputReportID REPORT_ID = OutputReportID::RequestStatus;
u8 rumble : 1;
u8 : 7;
};
@ -83,6 +117,8 @@ static_assert(sizeof(OutputReportRequestStatus) == 1, "Wrong size");
struct OutputReportWriteData
{
static constexpr OutputReportID REPORT_ID = OutputReportID::WriteData;
u8 rumble : 1;
u8 : 1;
u8 space : 2;
@ -100,6 +136,8 @@ static_assert(sizeof(OutputReportWriteData) == 21, "Wrong size");
struct OutputReportReadData
{
static constexpr OutputReportID REPORT_ID = OutputReportID::ReadData;
u8 rumble : 1;
u8 : 1;
u8 space : 2;
@ -116,6 +154,8 @@ static_assert(sizeof(OutputReportReadData) == 6, "Wrong size");
struct OutputReportSpeakerData
{
static constexpr OutputReportID REPORT_ID = OutputReportID::SpeakerData;
u8 rumble : 1;
u8 : 2;
u8 length : 5;
@ -157,6 +197,8 @@ static_assert(sizeof(ButtonData) == 2, "Wrong size");
struct InputReportStatus
{
static constexpr InputReportID REPORT_ID = InputReportID::Status;
ButtonData buttons;
u8 battery_low : 1;
u8 extension : 1;
@ -170,6 +212,8 @@ static_assert(sizeof(InputReportStatus) == 6, "Wrong size");
struct InputReportAck
{
static constexpr InputReportID REPORT_ID = InputReportID::Ack;
ButtonData buttons;
OutputReportID rpt_id;
ErrorCode error_code;
@ -178,6 +222,8 @@ static_assert(sizeof(InputReportAck) == 4, "Wrong size");
struct InputReportReadDataReply
{
static constexpr InputReportID REPORT_ID = InputReportID::ReadDataReply;
ButtonData buttons;
u8 error : 4;
u8 size_minus_one : 4;
@ -187,6 +233,64 @@ struct InputReportReadDataReply
};
static_assert(sizeof(InputReportReadDataReply) == 21, "Wrong size");
// Accel data handled as if there were always 10 bits of precision.
using AccelType = Common::TVec3<u16>;
using AccelData = ControllerEmu::RawValue<AccelType, 10>;
// Found in Wiimote EEPROM and Nunchuk "register".
// 0g and 1g points exist.
struct AccelCalibrationPoint
{
// All components have 10 bits of precision.
u16 GetX() const { return x2 << 2 | x1; }
u16 GetY() const { return y2 << 2 | y1; }
u16 GetZ() const { return z2 << 2 | z1; }
auto Get() const { return AccelType{GetX(), GetY(), GetZ()}; }
void SetX(u16 x)
{
x2 = x >> 2;
x1 = x;
}
void SetY(u16 y)
{
y2 = y >> 2;
y1 = y;
}
void SetZ(u16 z)
{
z2 = z >> 2;
z1 = z;
}
void Set(AccelType accel)
{
SetX(accel.x);
SetY(accel.y);
SetZ(accel.z);
}
u8 x2, y2, z2;
u8 z1 : 2;
u8 y1 : 2;
u8 x1 : 2;
u8 : 2;
};
// Located at 0x16 and 0x20 of Wii Remote EEPROM.
struct AccelCalibrationData
{
using Calibration = ControllerEmu::TwoPointCalibration<AccelType, 10>;
auto GetCalibration() const { return Calibration(zero_g.Get(), one_g.Get()); }
AccelCalibrationPoint zero_g;
AccelCalibrationPoint one_g;
u8 volume : 7;
u8 motor : 1;
u8 checksum;
};
} // namespace WiimoteCommon
#pragma pack(pop)

View File

@ -60,14 +60,6 @@ void CameraLogic::Update(const Common::Matrix44& transform)
using Common::Vec3;
using Common::Vec4;
constexpr int CAMERA_WIDTH = 1024;
constexpr int CAMERA_HEIGHT = 768;
// Wiibrew claims the camera FOV is about 33 deg by 23 deg.
// Unconfirmed but it seems to work well enough.
constexpr int CAMERA_FOV_X_DEG = 33;
constexpr int CAMERA_FOV_Y_DEG = 23;
constexpr auto CAMERA_FOV_Y = float(CAMERA_FOV_Y_DEG * MathUtil::TAU / 360);
constexpr auto CAMERA_ASPECT_RATIO = float(CAMERA_FOV_X_DEG) / CAMERA_FOV_Y_DEG;
@ -112,12 +104,12 @@ void CameraLogic::Update(const Common::Matrix44& transform)
if (point.z > 0)
{
// FYI: Casting down vs. rounding seems to produce more symmetrical output.
const auto x = s32((1 - point.x / point.w) * CAMERA_WIDTH / 2);
const auto y = s32((1 - point.y / point.w) * CAMERA_HEIGHT / 2);
const auto x = s32((1 - point.x / point.w) * CAMERA_RES_X / 2);
const auto y = s32((1 - point.y / point.w) * CAMERA_RES_Y / 2);
const auto point_size = std::lround(MAX_POINT_SIZE / point.w / 2);
if (x >= 0 && y >= 0 && x < CAMERA_WIDTH && y < CAMERA_HEIGHT)
if (x >= 0 && y >= 0 && x < CAMERA_RES_X && y < CAMERA_RES_Y)
return CameraPoint{u16(x), u16(y), u8(point_size)};
}
@ -165,7 +157,7 @@ void CameraLogic::Update(const Common::Matrix44& transform)
for (std::size_t i = 0; i != camera_points.size(); ++i)
{
const auto& p = camera_points[i];
if (p.x < CAMERA_WIDTH)
if (p.x < CAMERA_RES_X)
{
IRExtended irdata = {};
@ -186,7 +178,7 @@ void CameraLogic::Update(const Common::Matrix44& transform)
for (std::size_t i = 0; i != camera_points.size(); ++i)
{
const auto& p = camera_points[i];
if (p.x < CAMERA_WIDTH)
if (p.x < CAMERA_RES_X)
{
IRFull irdata = {};
@ -203,8 +195,8 @@ void CameraLogic::Update(const Common::Matrix44& transform)
irdata.xmin = std::max(p.x - p.size, 0);
irdata.ymin = std::max(p.y - p.size, 0);
irdata.xmax = std::min(p.x + p.size, CAMERA_WIDTH);
irdata.ymax = std::min(p.y + p.size, CAMERA_HEIGHT);
irdata.xmax = std::min(p.x + p.size, CAMERA_RES_X);
irdata.ymax = std::min(p.y + p.size, CAMERA_RES_Y);
// TODO: Is this maybe MSbs of the "intensity" value?
irdata.zero = 0;

View File

@ -20,6 +20,8 @@ namespace WiimoteEmu
// Four bytes for two objects. Filled with 0xFF if empty
struct IRBasic
{
using IRObject = Common::TVec2<u16>;
u8 x1;
u8 y1;
u8 x2hi : 2;
@ -28,6 +30,9 @@ struct IRBasic
u8 y1hi : 2;
u8 x2;
u8 y2;
auto GetObject1() const { return IRObject(x1hi << 8 | x1, y1hi << 8 | y1); }
auto GetObject2() const { return IRObject(x2hi << 8 | x2, y2hi << 8 | y2); }
};
static_assert(sizeof(IRBasic) == 5, "Wrong size");
@ -62,6 +67,14 @@ static_assert(sizeof(IRFull) == 9, "Wrong size");
class CameraLogic : public I2CSlave
{
public:
static constexpr int CAMERA_RES_X = 1024;
static constexpr int CAMERA_RES_Y = 768;
// Wiibrew claims the camera FOV is about 33 deg by 23 deg.
// Unconfirmed but it seems to work well enough.
static constexpr int CAMERA_FOV_X_DEG = 33;
static constexpr int CAMERA_FOV_Y_DEG = 23;
enum : u8
{
IR_MODE_BASIC = 1,

View File

@ -54,11 +54,15 @@ double CalculateStopDistance(double velocity, double max_accel)
return velocity * velocity / (2 * std::copysign(max_accel, velocity));
}
// Note that 'gyroscope' is rotation of world around device.
Common::Matrix33 ComplementaryFilter(const Common::Vec3& accelerometer,
const Common::Matrix33& gyroscope, float accel_weight)
} // namespace
namespace WiimoteEmu
{
const auto gyro_vec = gyroscope * Common::Vec3{0, 0, 1};
Common::Matrix33 ComplementaryFilter(const Common::Matrix33& gyroscope,
const Common::Vec3& accelerometer, float accel_weight,
const Common::Vec3& accelerometer_normal)
{
const auto gyro_vec = gyroscope * accelerometer_normal;
const auto normalized_accel = accelerometer.Normalized();
const auto cos_angle = normalized_accel.Dot(gyro_vec);
@ -76,10 +80,6 @@ Common::Matrix33 ComplementaryFilter(const Common::Vec3& accelerometer,
}
}
} // namespace
namespace WiimoteEmu
{
IMUCursorState::IMUCursorState() : rotation{Common::Matrix33::Identity()}
{
}
@ -203,17 +203,17 @@ void EmulateSwing(MotionState* state, ControllerEmu::Force* swing_group, float t
}
}
WiimoteCommon::DataReportBuilder::AccelData ConvertAccelData(const Common::Vec3& accel, u16 zero_g,
u16 one_g)
WiimoteCommon::AccelData ConvertAccelData(const Common::Vec3& accel, u16 zero_g, u16 one_g)
{
const auto scaled_accel = accel * (one_g - zero_g) / float(GRAVITY_ACCELERATION);
// 10-bit integers.
constexpr long MAX_VALUE = (1 << 10) - 1;
return {u16(std::clamp(std::lround(scaled_accel.x + zero_g), 0l, MAX_VALUE)),
u16(std::clamp(std::lround(scaled_accel.y + zero_g), 0l, MAX_VALUE)),
u16(std::clamp(std::lround(scaled_accel.z + zero_g), 0l, MAX_VALUE))};
return WiimoteCommon::AccelData(
{u16(std::clamp(std::lround(scaled_accel.x + zero_g), 0l, MAX_VALUE)),
u16(std::clamp(std::lround(scaled_accel.y + zero_g), 0l, MAX_VALUE)),
u16(std::clamp(std::lround(scaled_accel.z + zero_g), 0l, MAX_VALUE))});
}
void EmulateCursor(MotionState* state, ControllerEmu::Cursor* ir_group, float time_elapsed)
@ -311,28 +311,24 @@ void EmulateIMUCursor(IMUCursorState* state, ControllerEmu::IMUCursor* imu_ir_gr
}
// Apply rotation from gyro data.
const auto gyro_rotation = Common::Matrix33::FromQuaternion(ang_vel->x * time_elapsed / -2,
ang_vel->y * time_elapsed / -2,
ang_vel->z * time_elapsed / -2, 1);
const auto gyro_rotation = GetMatrixFromGyroscope(*ang_vel * -1 * time_elapsed);
state->rotation = gyro_rotation * state->rotation;
// If we have some non-zero accel data use it to adjust gyro drift.
constexpr auto ACCEL_WEIGHT = 0.02f;
auto const accel = imu_accelerometer_group->GetState().value_or(Common::Vec3{});
if (accel.LengthSquared())
state->rotation = ComplementaryFilter(accel, state->rotation, ACCEL_WEIGHT);
const auto inv_rotation = state->rotation.Inverted();
state->rotation = ComplementaryFilter(state->rotation, accel, ACCEL_WEIGHT);
// Clamp yaw within configured bounds.
const auto yaw = std::asin((inv_rotation * Common::Vec3{0, 1, 0}).x);
const auto yaw = GetYaw(state->rotation);
const auto max_yaw = float(imu_ir_group->GetTotalYaw() / 2);
auto target_yaw = std::clamp(yaw, -max_yaw, max_yaw);
// Handle the "Recenter" button being pressed.
if (imu_ir_group->controls[0]->GetState<bool>())
{
state->recentered_pitch = std::asin((inv_rotation * Common::Vec3{0, 1, 0}).z);
state->recentered_pitch = GetPitch(state->rotation);
target_yaw = 0;
}
@ -390,10 +386,33 @@ Common::Matrix33 GetMatrixFromAcceleration(const Common::Vec3& accel)
axis.LengthSquared() ? axis.Normalized() : Common::Vec3{0, 1, 0});
}
Common::Matrix33 GetMatrixFromGyroscope(const Common::Vec3& gyro)
{
return Common::Matrix33::FromQuaternion(gyro.x / 2, gyro.y / 2, gyro.z / 2, 1);
}
Common::Matrix33 GetRotationalMatrix(const Common::Vec3& angle)
{
return Common::Matrix33::RotateZ(angle.z) * Common::Matrix33::RotateY(angle.y) *
Common::Matrix33::RotateX(angle.x);
}
float GetPitch(const Common::Matrix33& world_rotation)
{
const auto vec = world_rotation * Common::Vec3{0, 0, 1};
return std::atan2(vec.y, Common::Vec2(vec.x, vec.z).Length());
}
float GetRoll(const Common::Matrix33& world_rotation)
{
const auto vec = world_rotation * Common::Vec3{0, 0, 1};
return std::atan2(vec.x, vec.z);
}
float GetYaw(const Common::Matrix33& world_rotation)
{
const auto vec = world_rotation.Inverted() * Common::Vec3{0, 1, 0};
return std::atan2(vec.x, vec.y);
}
} // namespace WiimoteEmu

View File

@ -54,12 +54,26 @@ struct MotionState : PositionalState, RotationalState
{
};
// Note that 'gyroscope' is rotation of world around device.
// Alternative accelerometer_normal can be supplied to correct from non-accelerometer data.
// e.g. Used for yaw/pitch correction with IR data.
Common::Matrix33 ComplementaryFilter(const Common::Matrix33& gyroscope,
const Common::Vec3& accelerometer, float accel_weight,
const Common::Vec3& accelerometer_normal = {0, 0, 1});
// Estimate orientation from accelerometer data.
Common::Matrix33 GetMatrixFromAcceleration(const Common::Vec3& accel);
// Get a rotation matrix from current gyro data.
Common::Matrix33 GetMatrixFromGyroscope(const Common::Vec3& gyro);
// Build a rotational matrix from euler angles.
Common::Matrix33 GetRotationalMatrix(const Common::Vec3& angle);
float GetPitch(const Common::Matrix33& world_rotation);
float GetRoll(const Common::Matrix33& world_rotation);
float GetYaw(const Common::Matrix33& world_rotation);
void ApproachPositionWithJerk(PositionalState* state, const Common::Vec3& target,
const Common::Vec3& max_jerk, float time_elapsed);
@ -75,7 +89,6 @@ void EmulateIMUCursor(IMUCursorState* state, ControllerEmu::IMUCursor* imu_ir_gr
ControllerEmu::IMUGyroscope* imu_gyroscope_group, float time_elapsed);
// Convert m/s/s acceleration data to the format used by Wiimote/Nunchuk (10-bit unsigned integers).
WiimoteCommon::DataReportBuilder::AccelData ConvertAccelData(const Common::Vec3& accel, u16 zero_g,
u16 one_g);
WiimoteCommon::AccelData ConvertAccelData(const Common::Vec3& accel, u16 zero_g, u16 one_g);
} // namespace WiimoteEmu

View File

@ -236,10 +236,6 @@ void Wiimote::HandleRequestStatus(const OutputReportRequestStatus&)
// Update status struct
m_status.extension = m_extension_port.IsDeviceConnected();
// Based on testing, old WiiLi.org docs, and WiiUse library:
// Max battery level seems to be 0xc8 (decimal 200)
constexpr u8 MAX_BATTERY_LEVEL = 0xc8;
m_status.battery = u8(std::lround(m_battery_setting.GetValue() / 100 * MAX_BATTERY_LEVEL));
if (Core::WantsDeterminism())

View File

@ -114,8 +114,10 @@ void Classic::Update()
{
const ControllerEmu::AnalogStick::StateData left_stick_state = m_left_stick->GetState();
classic_data.lx = static_cast<u8>(LEFT_STICK_CENTER + (left_stick_state.x * LEFT_STICK_RADIUS));
classic_data.ly = static_cast<u8>(LEFT_STICK_CENTER + (left_stick_state.y * LEFT_STICK_RADIUS));
const u8 x = static_cast<u8>(LEFT_STICK_CENTER + (left_stick_state.x * LEFT_STICK_RADIUS));
const u8 y = static_cast<u8>(LEFT_STICK_CENTER + (left_stick_state.y * LEFT_STICK_RADIUS));
classic_data.SetLeftStick({x, y});
}
// right stick
@ -125,10 +127,7 @@ void Classic::Update()
const u8 x = static_cast<u8>(RIGHT_STICK_CENTER + (right_stick_data.x * RIGHT_STICK_RADIUS));
const u8 y = static_cast<u8>(RIGHT_STICK_CENTER + (right_stick_data.y * RIGHT_STICK_RADIUS));
classic_data.rx1 = x;
classic_data.rx2 = x >> 1;
classic_data.rx3 = x >> 3;
classic_data.ry = y;
classic_data.SetRightStick({x, y});
}
// triggers
@ -139,18 +138,15 @@ void Classic::Update()
const u8 lt = static_cast<u8>(trigs[0] * TRIGGER_RANGE);
const u8 rt = static_cast<u8>(trigs[1] * TRIGGER_RANGE);
classic_data.lt1 = lt;
classic_data.lt2 = lt >> 3;
classic_data.rt = rt;
classic_data.SetLeftTrigger(lt);
classic_data.SetRightTrigger(rt);
}
// buttons
m_buttons->GetState(&classic_data.bt.hex, classic_button_bitmasks.data());
// dpad
m_dpad->GetState(&classic_data.bt.hex, classic_dpad_bitmasks.data());
// flip button bits
classic_data.bt.hex ^= 0xFFFF;
// buttons and dpad
u16 buttons = 0;
m_buttons->GetState(&buttons, classic_button_bitmasks.data());
m_dpad->GetState(&buttons, classic_dpad_bitmasks.data());
classic_data.SetButtons(buttons);
Common::BitCastPtr<DataFormat>(&m_reg.controller_data) = classic_data;
}

View File

@ -4,6 +4,9 @@
#pragma once
#include <limits>
#include "Common/Matrix.h"
#include "Core/HW/WiimoteCommon/WiimoteReport.h"
#include "Core/HW/WiimoteEmu/Extension/Extension.h"
@ -56,12 +59,59 @@ public:
};
static_assert(sizeof(ButtonFormat) == 2, "Wrong size");
static constexpr int LEFT_STICK_BITS = 6;
static constexpr int RIGHT_STICK_BITS = 5;
static constexpr int TRIGGER_BITS = 5;
struct DataFormat
{
// lx/ly/lz; left joystick
// rx/ry/rz; right joystick
// lt; left trigger
// rt; left trigger
using StickType = Common::TVec2<u8>;
using LeftStickRawValue = ControllerEmu::RawValue<StickType, LEFT_STICK_BITS>;
using RightStickRawValue = ControllerEmu::RawValue<StickType, RIGHT_STICK_BITS>;
using TriggerType = u8;
using TriggerRawValue = ControllerEmu::RawValue<TriggerType, TRIGGER_BITS>;
// 6-bit X and Y values (0-63)
auto GetLeftStick() const { return LeftStickRawValue{StickType(lx, ly)}; };
void SetLeftStick(const StickType& value)
{
lx = value.x;
ly = value.y;
}
// 5-bit X and Y values (0-31)
auto GetRightStick() const
{
return RightStickRawValue{StickType(rx1 | rx2 << 1 | rx3 << 3, ry)};
};
void SetRightStick(const StickType& value)
{
rx1 = value.x & 0b1;
rx2 = (value.x >> 1) & 0b11;
rx3 = (value.x >> 3) & 0b11;
ry = value.y;
}
// 5-bit values (0-31)
auto GetLeftTrigger() const { return TriggerRawValue(lt1 | lt2 << 3); }
void SetLeftTrigger(TriggerType value)
{
lt1 = value & 0b111;
lt2 = (value >> 3) & 0b11;
}
auto GetRightTrigger() const { return TriggerRawValue(rt); }
void SetRightTrigger(TriggerType value) { rt = value; }
u16 GetButtons() const
{
// 0 == pressed.
return ~bt.hex;
}
void SetButtons(u16 value)
{
// 0 == pressed.
bt.hex = ~value;
}
u8 lx : 6; // byte 0
u8 rx3 : 2;
@ -80,6 +130,53 @@ public:
};
static_assert(sizeof(DataFormat) == 6, "Wrong size");
static constexpr int CAL_STICK_BITS = 8;
static constexpr int CAL_TRIGGER_BITS = 8;
struct CalibrationData
{
using StickType = DataFormat::StickType;
using TriggerType = DataFormat::TriggerType;
using StickCalibration = ControllerEmu::ThreePointCalibration<StickType, CAL_STICK_BITS>;
using TriggerCalibration = ControllerEmu::TwoPointCalibration<TriggerType, CAL_TRIGGER_BITS>;
static constexpr TriggerType TRIGGER_MAX = std::numeric_limits<TriggerType>::max();
struct StickAxis
{
u8 max;
u8 min;
u8 center;
};
auto GetLeftStick() const
{
return StickCalibration{StickType{left_stick_x.min, left_stick_y.min},
StickType{left_stick_x.center, left_stick_y.center},
StickType{left_stick_x.max, left_stick_y.max}};
}
auto GetRightStick() const
{
return StickCalibration{StickType{right_stick_x.min, right_stick_y.min},
StickType{right_stick_x.center, right_stick_y.center},
StickType{right_stick_x.max, right_stick_y.max}};
}
auto GetLeftTrigger() const { return TriggerCalibration{left_trigger_zero, TRIGGER_MAX}; }
auto GetRightTrigger() const { return TriggerCalibration{right_trigger_zero, TRIGGER_MAX}; }
StickAxis left_stick_x;
StickAxis left_stick_y;
StickAxis right_stick_x;
StickAxis right_stick_y;
u8 left_trigger_zero;
u8 right_trigger_zero;
std::array<u8, 2> checksum;
};
static_assert(sizeof(CalibrationData) == 16, "Wrong size");
Classic();
void Update() override;
@ -110,13 +207,10 @@ public:
static constexpr u8 CAL_STICK_CENTER = 0x80;
static constexpr u8 CAL_STICK_RANGE = 0x7f;
static constexpr int CAL_STICK_BITS = 8;
static constexpr int LEFT_STICK_BITS = 6;
static constexpr u8 LEFT_STICK_CENTER = CAL_STICK_CENTER >> (CAL_STICK_BITS - LEFT_STICK_BITS);
static constexpr u8 LEFT_STICK_RADIUS = CAL_STICK_RANGE >> (CAL_STICK_BITS - LEFT_STICK_BITS);
static constexpr int RIGHT_STICK_BITS = 5;
static constexpr u8 RIGHT_STICK_CENTER = CAL_STICK_CENTER >> (CAL_STICK_BITS - RIGHT_STICK_BITS);
static constexpr u8 RIGHT_STICK_RADIUS = CAL_STICK_RANGE >> (CAL_STICK_BITS - RIGHT_STICK_BITS);

View File

@ -87,10 +87,9 @@ void Nunchuk::Update()
}
// buttons
m_buttons->GetState(&nc_data.bt.hex, nunchuk_button_bitmasks.data());
// flip the button bits :/
nc_data.bt.hex ^= 0x03;
u8 buttons = 0;
m_buttons->GetState(&buttons, nunchuk_button_bitmasks.data());
nc_data.SetButtons(buttons);
// Acceleration data:
EmulateSwing(&m_swing_state, m_swing, 1.f / ::Wiimote::UPDATE_FREQ);
@ -109,13 +108,7 @@ void Nunchuk::Update()
// Calibration values are 8-bit but we want 10-bit precision, so << 2.
const auto acc = ConvertAccelData(accel, ACCEL_ZERO_G << 2, ACCEL_ONE_G << 2);
nc_data.ax = (acc.x >> 2) & 0xFF;
nc_data.ay = (acc.y >> 2) & 0xFF;
nc_data.az = (acc.z >> 2) & 0xFF;
nc_data.bt.acc_x_lsb = acc.x & 0x3;
nc_data.bt.acc_y_lsb = acc.y & 0x3;
nc_data.bt.acc_z_lsb = acc.z & 0x3;
nc_data.SetAccel(acc.value);
Common::BitCastPtr<DataFormat>(&m_reg.controller_data) = nc_data;
}

View File

@ -51,25 +51,103 @@ public:
};
static_assert(sizeof(ButtonFormat) == 1, "Wrong size");
union DataFormat
struct DataFormat
{
struct
using StickType = Common::TVec2<u8>;
using StickRawValue = ControllerEmu::RawValue<StickType, 8>;
using AccelType = WiimoteCommon::AccelType;
using AccelData = WiimoteCommon::AccelData;
auto GetStick() const { return StickRawValue(StickType(jx, jy)); }
// Components have 10 bits of precision.
u16 GetAccelX() const { return ax << 2 | bt.acc_x_lsb; }
u16 GetAccelY() const { return ay << 2 | bt.acc_y_lsb; }
u16 GetAccelZ() const { return az << 2 | bt.acc_z_lsb; }
auto GetAccel() const { return AccelData{AccelType{GetAccelX(), GetAccelY(), GetAccelZ()}}; }
void SetAccelX(u16 val)
{
// joystick x, y
u8 jx;
u8 jy;
ax = val >> 2;
bt.acc_x_lsb = val & 0b11;
}
void SetAccelY(u16 val)
{
ay = val >> 2;
bt.acc_y_lsb = val & 0b11;
}
void SetAccelZ(u16 val)
{
az = val >> 2;
bt.acc_z_lsb = val & 0b11;
}
void SetAccel(const AccelType& accel)
{
SetAccelX(accel.x);
SetAccelY(accel.y);
SetAccelZ(accel.z);
}
// accelerometer
u8 ax;
u8 ay;
u8 az;
u8 GetButtons() const
{
// 0 == pressed.
return ~bt.hex & (BUTTON_C | BUTTON_Z);
}
void SetButtons(u8 value)
{
// 0 == pressed.
bt.hex |= (BUTTON_C | BUTTON_Z);
bt.hex ^= value & (BUTTON_C | BUTTON_Z);
}
// buttons + accelerometer LSBs
ButtonFormat bt;
};
// joystick x, y
u8 jx;
u8 jy;
// accelerometer
u8 ax;
u8 ay;
u8 az;
// buttons + accelerometer LSBs
ButtonFormat bt;
};
static_assert(sizeof(DataFormat) == 6, "Wrong size");
struct CalibrationData
{
using StickType = DataFormat::StickType;
using StickCalibration = ControllerEmu::ThreePointCalibration<StickType, 8>;
using AccelType = WiimoteCommon::AccelType;
using AccelCalibration = ControllerEmu::TwoPointCalibration<AccelType, 10>;
struct Stick
{
u8 max;
u8 min;
u8 center;
};
auto GetStick() const
{
return StickCalibration(StickType{stick_x.min, stick_y.min},
StickType{stick_x.center, stick_y.center},
StickType{stick_x.max, stick_y.max});
}
auto GetAccel() const { return AccelCalibration(accel_zero_g.Get(), accel_one_g.Get()); }
WiimoteCommon::AccelCalibrationPoint accel_zero_g;
WiimoteCommon::AccelCalibrationPoint accel_one_g;
Stick stick_x;
Stick stick_y;
std::array<u8, 2> checksum;
};
static_assert(sizeof(CalibrationData) == 16, "Wrong size");
Nunchuk();
void Update() override;

View File

@ -16,7 +16,6 @@
#include "Common/Logging/Log.h"
#include "Common/MathUtil.h"
#include "Common/MsgHandler.h"
#include "Common/Swap.h"
#include "Core/HW/Wiimote.h"
#include "Core/HW/WiimoteEmu/Dynamics.h"
@ -56,6 +55,41 @@ struct MPI : mbedtls_mpi
namespace WiimoteEmu
{
Common::Vec3 MotionPlus::DataFormat::Data::GetAngularVelocity(const CalibrationBlocks& blocks) const
{
// Each axis may be using either slow or fast calibration.
const auto calibration = blocks.GetRelevantCalibration(is_slow);
// It seems M+ calibration data does not follow the "right-hand rule".
const auto sign_fix = Common::Vec3(-1, +1, -1);
// Adjust deg/s to rad/s.
constexpr auto scalar = float(MathUtil::TAU / 360);
return gyro.GetNormalizedValue(calibration.value) * sign_fix * Common::Vec3(calibration.degrees) *
scalar;
}
auto MotionPlus::CalibrationBlocks::GetRelevantCalibration(SlowType is_slow) const
-> RelevantCalibration
{
RelevantCalibration result;
const auto& pitch_block = is_slow.x ? slow : fast;
const auto& roll_block = is_slow.y ? slow : fast;
const auto& yaw_block = is_slow.z ? slow : fast;
result.value.max = {pitch_block.pitch_scale, roll_block.roll_scale, yaw_block.yaw_scale};
result.value.zero = {pitch_block.pitch_zero, roll_block.roll_zero, yaw_block.yaw_zero};
result.degrees.x = pitch_block.degrees_div_6 * 6;
result.degrees.y = roll_block.degrees_div_6 * 6;
result.degrees.z = yaw_block.degrees_div_6 * 6;
return result;
}
MotionPlus::MotionPlus() : Extension("MotionPlus")
{
}
@ -82,35 +116,20 @@ void MotionPlus::Reset()
constexpr u16 ROLL_SCALE = CALIBRATION_ZERO + CALIBRATION_SCALE_OFFSET;
constexpr u16 PITCH_SCALE = CALIBRATION_ZERO - CALIBRATION_SCALE_OFFSET;
#pragma pack(push, 1)
struct CalibrationBlock
{
u16 yaw_zero = Common::swap16(CALIBRATION_ZERO);
u16 roll_zero = Common::swap16(CALIBRATION_ZERO);
u16 pitch_zero = Common::swap16(CALIBRATION_ZERO);
u16 yaw_scale = Common::swap16(YAW_SCALE);
u16 roll_scale = Common::swap16(ROLL_SCALE);
u16 pitch_scale = Common::swap16(PITCH_SCALE);
u8 degrees_div_6;
};
struct CalibrationData
{
CalibrationBlock fast;
u8 uid_1;
Common::BigEndianValue<u16> crc32_msb;
CalibrationBlock slow;
u8 uid_2;
Common::BigEndianValue<u16> crc32_lsb;
};
#pragma pack(pop)
static_assert(sizeof(CalibrationData) == 0x20, "Bad size.");
static_assert(CALIBRATION_FAST_SCALE_DEGREES % 6 == 0, "Value should be divisible by 6.");
static_assert(CALIBRATION_SLOW_SCALE_DEGREES % 6 == 0, "Value should be divisible by 6.");
CalibrationData calibration;
calibration.fast.yaw_zero = calibration.slow.yaw_zero = CALIBRATION_ZERO;
calibration.fast.roll_zero = calibration.slow.roll_zero = CALIBRATION_ZERO;
calibration.fast.pitch_zero = calibration.slow.pitch_zero = CALIBRATION_ZERO;
calibration.fast.yaw_scale = calibration.slow.yaw_scale = YAW_SCALE;
calibration.fast.roll_scale = calibration.slow.roll_scale = ROLL_SCALE;
calibration.fast.pitch_scale = calibration.slow.pitch_scale = PITCH_SCALE;
calibration.fast.degrees_div_6 = CALIBRATION_FAST_SCALE_DEGREES / 6;
calibration.slow.degrees_div_6 = CALIBRATION_SLOW_SCALE_DEGREES / 6;
@ -120,17 +139,22 @@ void MotionPlus::Reset()
calibration.uid_1 = 0x0b;
calibration.uid_2 = 0xe9;
// Update checksum (crc32 of all data other than the checksum itself):
auto crc_result = crc32(0, Z_NULL, 0);
crc_result = crc32(crc_result, reinterpret_cast<const Bytef*>(&calibration), 0xe);
crc_result = crc32(crc_result, reinterpret_cast<const Bytef*>(&calibration) + 0x10, 0xe);
calibration.crc32_lsb = u16(crc_result);
calibration.crc32_msb = u16(crc_result >> 16);
calibration.UpdateChecksum();
Common::BitCastPtr<CalibrationData>(m_reg_data.calibration_data.data()) = calibration;
}
void MotionPlus::CalibrationData::UpdateChecksum()
{
// Checksum is crc32 of all data other than the checksum itself.
auto crc_result = crc32(0, Z_NULL, 0);
crc_result = crc32(crc_result, reinterpret_cast<const Bytef*>(this), 0xe);
crc_result = crc32(crc_result, reinterpret_cast<const Bytef*>(this) + 0x10, 0xe);
crc32_lsb = u16(crc_result);
crc32_msb = u16(crc_result >> 16);
}
void MotionPlus::DoState(PointerWrap& p)
{
p.Do(m_reg_data);
@ -547,47 +571,10 @@ void MotionPlus::PrepareInput(const Common::Vec3& angular_velocity)
break;
}
case PassthroughMode::Nunchuk:
{
if (EXT_AMT == m_i2c_bus.BusRead(EXT_SLAVE, EXT_ADDR, EXT_AMT, data))
{
// Passthrough data modifications via wiibrew.org
// Verified on real hardware via a test of every bit.
// Data passing through drops the least significant bit of the three accelerometer values.
// Bit 7 of byte 5 is moved to bit 6 of byte 5, overwriting it
Common::SetBit(data[5], 6, Common::ExtractBit(data[5], 7));
// Bit 0 of byte 4 is moved to bit 7 of byte 5
Common::SetBit(data[5], 7, Common::ExtractBit(data[4], 0));
// Bit 3 of byte 5 is moved to bit 4 of byte 5, overwriting it
Common::SetBit(data[5], 4, Common::ExtractBit(data[5], 3));
// Bit 1 of byte 5 is moved to bit 3 of byte 5
Common::SetBit(data[5], 3, Common::ExtractBit(data[5], 1));
// Bit 0 of byte 5 is moved to bit 2 of byte 5, overwriting it
Common::SetBit(data[5], 2, Common::ExtractBit(data[5], 0));
mplus_data = Common::BitCastPtr<DataFormat>(data);
// Bit 0 and 1 of byte 5 contain a M+ flag and a zero bit which is set below.
mplus_data.is_mp_data = false;
}
else
{
// Read failed (extension unplugged), Send M+ data instead
mplus_data.is_mp_data = true;
}
break;
}
case PassthroughMode::Classic:
{
if (EXT_AMT == m_i2c_bus.BusRead(EXT_SLAVE, EXT_ADDR, EXT_AMT, data))
{
// Passthrough data modifications via wiibrew.org
// Verified on real hardware via a test of every bit.
// Data passing through drops the least significant bit of the axes of the left (or only)
// joystick Bit 0 of Byte 4 is overwritten [by the 'extension_connected' flag] Bits 0 and
// 1 of Byte 5 are moved to bit 0 of Bytes 0 and 1, overwriting what was there before.
Common::SetBit(data[0], 0, Common::ExtractBit(data[5], 0));
Common::SetBit(data[1], 0, Common::ExtractBit(data[5], 1));
ApplyPassthroughModifications(GetPassthroughMode(), data);
mplus_data = Common::BitCastPtr<DataFormat>(data);
// Bit 0 and 1 of byte 5 contain a M+ flag and a zero bit which is set below.
@ -599,7 +586,6 @@ void MotionPlus::PrepareInput(const Common::Vec3& angular_velocity)
mplus_data.is_mp_data = true;
}
break;
}
default:
// This really shouldn't happen as the M+ deactivates on an invalid mode write.
ERROR_LOG(WIIMOTE, "M+ unknown passthrough-mode %d", int(GetPassthroughMode()));
@ -664,4 +650,66 @@ void MotionPlus::PrepareInput(const Common::Vec3& angular_velocity)
Common::BitCastPtr<DataFormat>(data) = mplus_data;
}
void MotionPlus::ApplyPassthroughModifications(PassthroughMode mode, u8* data)
{
if (mode == PassthroughMode::Nunchuk)
{
// Passthrough data modifications via wiibrew.org
// Verified on real hardware via a test of every bit.
// Data passing through drops the least significant bit of the three accelerometer values.
// Bit 7 of byte 5 is moved to bit 6 of byte 5, overwriting it
Common::SetBit<6>(data[5], Common::ExtractBit<7>(data[5]));
// Bit 0 of byte 4 is moved to bit 7 of byte 5
Common::SetBit<7>(data[5], Common::ExtractBit<0>(data[4]));
// Bit 3 of byte 5 is moved to bit 4 of byte 5, overwriting it
Common::SetBit<4>(data[5], Common::ExtractBit<3>(data[5]));
// Bit 1 of byte 5 is moved to bit 3 of byte 5
Common::SetBit<3>(data[5], Common::ExtractBit<1>(data[5]));
// Bit 0 of byte 5 is moved to bit 2 of byte 5, overwriting it
Common::SetBit<2>(data[5], Common::ExtractBit<0>(data[5]));
}
else if (mode == PassthroughMode::Classic)
{
// Passthrough data modifications via wiibrew.org
// Verified on real hardware via a test of every bit.
// Data passing through drops the least significant bit of the axes of the left (or only)
// joystick Bit 0 of Byte 4 is overwritten [by the 'extension_connected' flag] Bits 0 and
// 1 of Byte 5 are moved to bit 0 of Bytes 0 and 1, overwriting what was there before.
Common::SetBit<0>(data[0], Common::ExtractBit<0>(data[5]));
Common::SetBit<0>(data[1], Common::ExtractBit<1>(data[5]));
}
}
void MotionPlus::ReversePassthroughModifications(PassthroughMode mode, u8* data)
{
if (mode == PassthroughMode::Nunchuk)
{
// Undo M+'s "nunchuk passthrough" modifications.
Common::SetBit<0>(data[5], Common::ExtractBit<2>(data[5]));
Common::SetBit<1>(data[5], Common::ExtractBit<3>(data[5]));
Common::SetBit<3>(data[5], Common::ExtractBit<4>(data[5]));
Common::SetBit<0>(data[4], Common::ExtractBit<7>(data[5]));
Common::SetBit<7>(data[5], Common::ExtractBit<6>(data[5]));
// Set the overwritten bits from the next LSB.
Common::SetBit<2>(data[5], Common::ExtractBit<3>(data[5]));
Common::SetBit<4>(data[5], Common::ExtractBit<5>(data[5]));
Common::SetBit<6>(data[5], Common::ExtractBit<7>(data[5]));
}
else if (mode == PassthroughMode::Classic)
{
// Undo M+'s "classic controller passthrough" modifications.
Common::SetBit<0>(data[5], Common::ExtractBit<0>(data[0]));
Common::SetBit<1>(data[5], Common::ExtractBit<0>(data[1]));
// Set the overwritten bits from the next LSB.
Common::SetBit<0>(data[0], Common::ExtractBit<1>(data[0]));
Common::SetBit<0>(data[1], Common::ExtractBit<1>(data[1]));
// This is an overwritten unused button bit on the Classic Controller.
// Note it's a significant bit on the DJ Hero Turntable. (passthrough not feasible)
Common::SetBit<0>(data[4], 1);
}
}
} // namespace WiimoteEmu

View File

@ -7,59 +7,90 @@
#include <array>
#include "Common/CommonTypes.h"
#include "Common/Swap.h"
#include "Core/HW/WiimoteEmu/Dynamics.h"
#include "Core/HW/WiimoteEmu/ExtensionPort.h"
#include "Core/HW/WiimoteEmu/I2CBus.h"
namespace WiimoteEmu
{
struct AngularVelocity;
struct MotionPlus : public Extension
{
public:
MotionPlus();
void Update() override;
void Reset() override;
void DoState(PointerWrap& p) override;
ExtensionPort& GetExtPort();
// Vec3 is interpreted as radians/s about the x,y,z axes following the "right-hand rule".
void PrepareInput(const Common::Vec3& angular_velocity);
private:
enum class ChallengeState : u8
{
// Note: This is not a value seen on a real M+.
// Used to emulate activation state during which the M+ is not responsive.
Activating = 0x00,
PreparingX = 0x02,
ParameterXReady = 0x0e,
PreparingY = 0x14,
ParameterYReady = 0x1a,
};
enum class PassthroughMode : u8
{
// Note: `Disabled` is an M+ enabled with no passthrough. Maybe there is a better name.
Disabled = 0x04,
Nunchuk = 0x05,
Classic = 0x07,
};
enum class ActivationStatus
#pragma pack(push, 1)
struct CalibrationBlock
{
Inactive,
Activating,
Deactivating,
Active,
Common::BigEndianValue<u16> yaw_zero;
Common::BigEndianValue<u16> roll_zero;
Common::BigEndianValue<u16> pitch_zero;
Common::BigEndianValue<u16> yaw_scale;
Common::BigEndianValue<u16> roll_scale;
Common::BigEndianValue<u16> pitch_scale;
u8 degrees_div_6;
};
#pragma pack(push, 1)
struct CalibrationBlocks
{
using GyroType = Common::TVec3<u16>;
using SlowType = Common::TVec3<bool>;
struct RelevantCalibration
{
ControllerEmu::TwoPointCalibration<GyroType, 16> value;
Common::TVec3<u16> degrees;
};
// Each axis may be using either slow or fast calibration.
// This function builds calibration that is relevant for current data.
RelevantCalibration GetRelevantCalibration(SlowType is_slow) const;
CalibrationBlock fast;
CalibrationBlock slow;
};
struct CalibrationData
{
void UpdateChecksum();
CalibrationBlock fast;
u8 uid_1;
Common::BigEndianValue<u16> crc32_msb;
CalibrationBlock slow;
u8 uid_2;
Common::BigEndianValue<u16> crc32_lsb;
};
static_assert(sizeof(CalibrationData) == 0x20, "Wrong size");
struct DataFormat
{
using GyroType = CalibrationBlocks::GyroType;
using SlowType = CalibrationBlocks::SlowType;
using GyroRawValue = ControllerEmu::RawValue<GyroType, 14>;
struct Data
{
// Return radian/s following "right-hand rule" with given calibration blocks.
Common::Vec3 GetAngularVelocity(const CalibrationBlocks&) const;
GyroRawValue gyro;
SlowType is_slow;
};
auto GetData() const
{
return Data{
GyroRawValue{GyroType(pitch1 | pitch2 << 8, roll1 | roll2 << 8, yaw1 | yaw2 << 8)},
SlowType(pitch_slow, roll_slow, yaw_slow)};
}
// yaw1, roll1, pitch1: Bits 0-7
// yaw2, roll2, pitch2: Bits 8-13
@ -79,7 +110,50 @@ private:
u8 is_mp_data : 1;
u8 pitch2 : 6;
};
static_assert(sizeof(DataFormat) == 6, "Wrong size");
#pragma pack(pop)
static constexpr u8 INACTIVE_DEVICE_ADDR = 0x53;
static constexpr u8 ACTIVE_DEVICE_ADDR = 0x52;
static constexpr u8 PASSTHROUGH_MODE_OFFSET = 0xfe;
MotionPlus();
void Update() override;
void Reset() override;
void DoState(PointerWrap& p) override;
ExtensionPort& GetExtPort();
// Vec3 is interpreted as radians/s about the x,y,z axes following the "right-hand rule".
void PrepareInput(const Common::Vec3& angular_velocity);
// Pointer to 6 bytes is expected.
static void ApplyPassthroughModifications(PassthroughMode, u8* data);
static void ReversePassthroughModifications(PassthroughMode, u8* data);
private:
enum class ChallengeState : u8
{
// Note: This is not a value seen on a real M+.
// Used to emulate activation state during which the M+ is not responsive.
Activating = 0x00,
PreparingX = 0x02,
ParameterXReady = 0x0e,
PreparingY = 0x14,
ParameterYReady = 0x1a,
};
enum class ActivationStatus
{
Inactive,
Activating,
Deactivating,
Active,
};
#pragma pack(push, 1)
struct Register
{
std::array<u8, 21> controller_data;
@ -135,14 +209,8 @@ private:
std::array<u8, 6> ext_identifier;
};
#pragma pack(pop)
static_assert(sizeof(DataFormat) == 6, "Wrong size");
static_assert(0x100 == sizeof(Register), "Wrong size");
static constexpr u8 INACTIVE_DEVICE_ADDR = 0x53;
static constexpr u8 ACTIVE_DEVICE_ADDR = 0x52;
static constexpr u8 PASSTHROUGH_MODE_OFFSET = 0xfe;
static constexpr int CALIBRATION_BITS = 16;
static constexpr u16 CALIBRATION_ZERO = 1 << (CALIBRATION_BITS - 1);

View File

@ -500,7 +500,7 @@ void Wiimote::SendDataReport()
if (rpt_builder.HasAccel())
{
// Calibration values are 8-bit but we want 10-bit precision, so << 2.
DataReportBuilder::AccelData accel =
AccelData accel =
ConvertAccelData(GetTotalAcceleration(), ACCEL_ZERO_G << 2, ACCEL_ONE_G << 2);
rpt_builder.SetAccelData(accel);
}

View File

@ -257,12 +257,13 @@ void WiimoteDarwin::DisablePowerAssertionInternal()
@implementation SearchBT
- (void)deviceInquiryComplete:(IOBluetoothDeviceInquiry*)sender
error:(IOReturn)error
aborted:(BOOL)aborted {
aborted:(BOOL)aborted
{
done = true;
}
- (void)deviceInquiryDeviceFound:(IOBluetoothDeviceInquiry*)sender
device:(IOBluetoothDevice*)device {
- (void)deviceInquiryDeviceFound:(IOBluetoothDeviceInquiry*)sender device:(IOBluetoothDevice*)device
{
NOTICE_LOG(WIIMOTE, "Discovered Bluetooth device at %s: %s", [[device addressString] UTF8String],
[[device name] UTF8String]);
@ -274,11 +275,12 @@ void WiimoteDarwin::DisablePowerAssertionInternal()
@implementation ConnectBT
- (void)l2capChannelData:(IOBluetoothL2CAPChannel*)l2capChannel
data:(unsigned char*)data
length:(NSUInteger)length {
length:(NSUInteger)length
{
IOBluetoothDevice* device = [l2capChannel device];
WiimoteReal::WiimoteDarwin* wm = nullptr;
std::lock_guard<std::mutex> lk(WiimoteReal::g_wiimotes_mutex);
std::lock_guard lk(WiimoteReal::g_wiimotes_mutex);
for (int i = 0; i < MAX_WIIMOTES; i++)
{
@ -314,11 +316,12 @@ void WiimoteDarwin::DisablePowerAssertionInternal()
CFRunLoopStop(CFRunLoopGetCurrent());
}
- (void)l2capChannelClosed:(IOBluetoothL2CAPChannel*)l2capChannel {
- (void)l2capChannelClosed:(IOBluetoothL2CAPChannel*)l2capChannel
{
IOBluetoothDevice* device = [l2capChannel device];
WiimoteReal::WiimoteDarwin* wm = nullptr;
std::lock_guard<std::mutex> lk(WiimoteReal::g_wiimotes_mutex);
std::lock_guard lk(WiimoteReal::g_wiimotes_mutex);
for (int i = 0; i < MAX_WIIMOTES; i++)
{

View File

@ -26,6 +26,7 @@
#include "Core/HW/WiimoteReal/IOWin.h"
#include "Core/HW/WiimoteReal/IOdarwin.h"
#include "Core/HW/WiimoteReal/IOhidapi.h"
#include "InputCommon/ControllerInterface/Wiimote/Wiimote.h"
#include "InputCommon/InputConfig.h"
#include "SFML/Network.hpp"
@ -35,7 +36,7 @@ namespace WiimoteReal
using namespace WiimoteCommon;
static void TryToConnectBalanceBoard(std::unique_ptr<Wiimote>);
static void TryToConnectWiimote(std::unique_ptr<Wiimote>);
static bool TryToConnectWiimoteToSlot(std::unique_ptr<Wiimote>&, unsigned int);
static void HandleWiimoteDisconnect(int index);
static bool g_real_wiimotes_initialized = false;
@ -45,7 +46,7 @@ static bool g_real_wiimotes_initialized = false;
static std::unordered_set<std::string> s_known_ids;
static std::mutex s_known_ids_mutex;
std::mutex g_wiimotes_mutex;
std::recursive_mutex g_wiimotes_mutex;
// Real wii remotes assigned to a particular slot.
std::unique_ptr<Wiimote> g_wiimotes[MAX_BBMOTES];
@ -72,22 +73,64 @@ std::vector<WiimotePoolEntry> g_wiimote_pool;
WiimoteScanner g_wiimote_scanner;
static void ProcessWiimotePool()
// Attempt to fill a real wiimote slot from the pool or by stealing from ControllerInterface.
static void TryToFillWiimoteSlot(u32 index)
{
std::lock_guard<std::mutex> wm_lk(g_wiimotes_mutex);
std::lock_guard lk(g_wiimotes_mutex);
for (auto it = g_wiimote_pool.begin(); it != g_wiimote_pool.end();)
if (g_wiimotes[index] || WiimoteCommon::GetSource(index) != WiimoteSource::Real)
return;
// If the pool is empty, attempt to steal from ControllerInterface.
if (g_wiimote_pool.empty())
{
if (it->IsExpired())
{
INFO_LOG(WIIMOTE, "Removing expired wiimote pool entry.");
it = g_wiimote_pool.erase(it);
}
else
{
++it;
}
ciface::Wiimote::ReleaseDevices(1);
// Still empty?
if (g_wiimote_pool.empty())
return;
}
if (TryToConnectWiimoteToSlot(g_wiimote_pool.front().wiimote, index))
g_wiimote_pool.erase(g_wiimote_pool.begin());
}
// Attempts to fill enabled real wiimote slots.
// Push/pull wiimotes to/from ControllerInterface as needed.
void ProcessWiimotePool()
{
std::lock_guard lk(g_wiimotes_mutex);
for (u32 index = 0; index != MAX_WIIMOTES; ++index)
TryToFillWiimoteSlot(index);
if (SConfig::GetInstance().connect_wiimotes_for_ciface)
{
for (auto& entry : g_wiimote_pool)
ciface::Wiimote::AddDevice(std::move(entry.wiimote));
g_wiimote_pool.clear();
}
else
{
ciface::Wiimote::ReleaseDevices();
}
}
void AddWiimoteToPool(std::unique_ptr<Wiimote> wiimote)
{
// Our real wiimote class requires an index.
// Within the pool it's only going to be used for logging purposes.
static constexpr int POOL_WIIMOTE_INDEX = 99;
if (!wiimote->Connect(POOL_WIIMOTE_INDEX))
{
ERROR_LOG(WIIMOTE, "Failed to connect real wiimote.");
return;
}
std::lock_guard lk(g_wiimotes_mutex);
g_wiimote_pool.emplace_back(WiimotePoolEntry{std::move(wiimote)});
}
Wiimote::Wiimote() = default;
@ -165,7 +208,7 @@ void Wiimote::ResetDataReporting()
OutputReportMode rpt = {};
rpt.mode = InputReportID::ReportCore;
rpt.continuous = 0;
QueueReport(OutputReportID::ReportMode, &rpt, sizeof(rpt));
QueueReport(rpt);
}
void Wiimote::ClearReadQueue()
@ -241,11 +284,11 @@ void Wiimote::InterruptChannel(const u16 channel, const void* const data, const
else if (rpt[1] == u8(OutputReportID::SpeakerData) &&
(!SConfig::GetInstance().m_WiimoteEnableSpeaker || !m_speaker_enable || m_speaker_mute))
{
rpt.resize(3);
// Translate undesired speaker data reports into rumble reports.
rpt[1] = u8(OutputReportID::Rumble);
// Keep only the rumble bit.
rpt[2] &= 0x1;
rpt.resize(3);
}
WriteReport(std::move(rpt));
@ -380,11 +423,16 @@ static bool IsDataReport(const Report& rpt)
return rpt.size() >= 2 && rpt[1] >= u8(InputReportID::ReportCore);
}
bool Wiimote::GetNextReport(Report* report)
{
return m_read_reports.Pop(*report);
}
// Returns the next report that should be sent
Report& Wiimote::ProcessReadQueue()
{
// Pop through the queued reports
while (m_read_reports.Pop(m_last_input_report))
while (GetNextReport(&m_last_input_report))
{
if (!IsDataReport(m_last_input_report))
{
@ -452,26 +500,16 @@ void Wiimote::Prepare()
bool Wiimote::PrepareOnThread()
{
// core buttons, no continuous reporting
// TODO: use the structs..
u8 static const mode_report[] = {WR_SET_REPORT | BT_OUTPUT, u8(OutputReportID::ReportMode), 0,
// Set reporting mode to non-continuous core buttons and turn on rumble.
u8 static const mode_report[] = {WR_SET_REPORT | BT_OUTPUT, u8(OutputReportID::ReportMode), 1,
u8(InputReportID::ReportCore)};
// Set the active LEDs and turn on rumble.
u8 static led_report[] = {WR_SET_REPORT | BT_OUTPUT, u8(OutputReportID::LED), 0};
led_report[2] = u8(u8(LED::LED_1) << (m_index % WIIMOTE_BALANCE_BOARD) | 0x1);
// Turn off rumble
u8 static const rumble_report[] = {WR_SET_REPORT | BT_OUTPUT, u8(OutputReportID::Rumble), 0};
// Request status report
// Request status and turn off rumble.
u8 static const req_status_report[] = {WR_SET_REPORT | BT_OUTPUT,
u8(OutputReportID::RequestStatus), 0};
// TODO: check for sane response?
return (IOWrite(mode_report, sizeof(mode_report)) && IOWrite(led_report, sizeof(led_report)) &&
(Common::SleepCurrentThread(200), IOWrite(rumble_report, sizeof(rumble_report))) &&
IOWrite(req_status_report, sizeof(req_status_report)));
return IOWrite(mode_report, sizeof(mode_report)) &&
(Common::SleepCurrentThread(200), IOWrite(req_status_report, sizeof(req_status_report)));
}
void Wiimote::EmuStart()
@ -499,32 +537,20 @@ void Wiimote::EmuPause()
DisablePowerAssertionInternal();
}
static unsigned int CalculateConnectedWiimotes()
{
std::lock_guard<std::mutex> lk(g_wiimotes_mutex);
unsigned int connected_wiimotes = 0;
for (unsigned int i = 0; i < MAX_WIIMOTES; ++i)
if (g_wiimotes[i])
++connected_wiimotes;
return connected_wiimotes;
}
static unsigned int CalculateWantedWiimotes()
{
std::lock_guard<std::mutex> lk(g_wiimotes_mutex);
std::lock_guard lk(g_wiimotes_mutex);
// Figure out how many real Wiimotes are required
unsigned int wanted_wiimotes = 0;
for (unsigned int i = 0; i < MAX_WIIMOTES; ++i)
if (WiimoteCommon::GetSource(i) == WiimoteSource::Real && !g_wiimotes[i])
++wanted_wiimotes;
return wanted_wiimotes;
}
static unsigned int CalculateWantedBB()
{
std::lock_guard<std::mutex> lk(g_wiimotes_mutex);
std::lock_guard lk(g_wiimotes_mutex);
unsigned int wanted_bb = 0;
if (WiimoteCommon::GetSource(WIIMOTE_BALANCE_BOARD) == WiimoteSource::Real &&
!g_wiimotes[WIIMOTE_BALANCE_BOARD])
@ -564,14 +590,63 @@ bool WiimoteScanner::IsReady() const
static void CheckForDisconnectedWiimotes()
{
std::lock_guard<std::mutex> lk(g_wiimotes_mutex);
std::lock_guard lk(g_wiimotes_mutex);
for (unsigned int i = 0; i < MAX_BBMOTES; ++i)
if (g_wiimotes[i] && !g_wiimotes[i]->IsConnected())
HandleWiimoteDisconnect(i);
}
void WiimoteScanner::PoolThreadFunc()
{
Common::SetCurrentThreadName("Wiimote Pool Thread");
// Toggle between 1010 and 0101.
u8 led_value = 0b1010;
auto next_time = std::chrono::steady_clock::now();
while (m_scan_thread_running.IsSet())
{
std::this_thread::sleep_until(next_time);
next_time += std::chrono::milliseconds(250);
std::lock_guard lk(g_wiimotes_mutex);
// Remove stale pool entries.
for (auto it = g_wiimote_pool.begin(); it != g_wiimote_pool.end();)
{
if (!it->wiimote->IsConnected())
{
INFO_LOG(WIIMOTE, "Removing disconnected wiimote pool entry.");
it = g_wiimote_pool.erase(it);
}
else if (it->IsExpired())
{
INFO_LOG(WIIMOTE, "Removing expired wiimote pool entry.");
it = g_wiimote_pool.erase(it);
}
else
{
++it;
}
}
// Make wiimote pool LEDs dance.
for (auto& wiimote : g_wiimote_pool)
{
OutputReportLeds leds = {};
leds.leds = led_value;
wiimote.wiimote->QueueReport(leds);
}
led_value ^= 0b1111;
}
}
void WiimoteScanner::ThreadFunc()
{
std::thread pool_thread(&WiimoteScanner::PoolThreadFunc, this);
Common::SetCurrentThreadName("Wiimote Scanning Thread");
NOTICE_LOG(WIIMOTE, "Wiimote scanning thread has started.");
@ -594,22 +669,28 @@ void WiimoteScanner::ThreadFunc()
{
m_scan_mode_changed_event.WaitFor(std::chrono::milliseconds(500));
ProcessWiimotePool();
// Does stuff needed to detect disconnects on Windows
for (const auto& backend : m_backends)
backend->Update();
CheckForDisconnectedWiimotes();
if (m_scan_mode.load() == WiimoteScanMode::DO_NOT_SCAN)
continue;
if (!g_real_wiimotes_initialized)
continue;
// If we don't want Wiimotes in ControllerInterface, we may not need them at all.
if (!SConfig::GetInstance().connect_wiimotes_for_ciface)
{
// We don't want any remotes in passthrough mode or running in GC mode.
const bool core_running = Core::GetState() != Core::State::Uninitialized;
if (SConfig::GetInstance().m_bt_passthrough_enabled ||
(core_running && !SConfig::GetInstance().bWii))
continue;
// Does stuff needed to detect disconnects on Windows
for (const auto& backend : m_backends)
backend->Update();
if (0 == CalculateWantedWiimotes() && 0 == CalculateWantedBB())
continue;
// We don't want any remotes if we already connected everything we need.
if (0 == CalculateWantedWiimotes() && 0 == CalculateWantedBB())
continue;
}
for (const auto& backend : m_backends)
{
@ -617,7 +698,7 @@ void WiimoteScanner::ThreadFunc()
Wiimote* found_board = nullptr;
backend->FindWiimotes(found_wiimotes, found_board);
{
std::lock_guard<std::mutex> wm_lk(g_wiimotes_mutex);
std::unique_lock wm_lk(g_wiimotes_mutex);
for (auto* wiimote : found_wiimotes)
{
@ -626,7 +707,8 @@ void WiimoteScanner::ThreadFunc()
s_known_ids.insert(wiimote->GetId());
}
TryToConnectWiimote(std::unique_ptr<Wiimote>(wiimote));
AddWiimoteToPool(std::unique_ptr<Wiimote>(wiimote));
ProcessWiimotePool();
}
if (found_board)
@ -641,32 +723,32 @@ void WiimoteScanner::ThreadFunc()
}
}
if (m_scan_mode.load() == WiimoteScanMode::SCAN_ONCE)
m_scan_mode.store(WiimoteScanMode::DO_NOT_SCAN);
// Stop scanning if not in continous mode.
auto scan_mode = WiimoteScanMode::SCAN_ONCE;
m_scan_mode.compare_exchange_strong(scan_mode, WiimoteScanMode::DO_NOT_SCAN);
}
{
std::lock_guard<std::mutex> lg(m_backends_mutex);
m_backends.clear();
}
pool_thread.join();
NOTICE_LOG(WIIMOTE, "Wiimote scanning thread has stopped.");
}
bool Wiimote::Connect(int index)
{
m_index = index;
m_need_prepare.Set();
if (!m_run_thread.IsSet())
{
m_need_prepare.Set();
m_run_thread.Set();
StartThread();
m_thread_ready_event.Wait();
}
else
{
IOWakeup();
}
return IsConnected();
}
@ -729,6 +811,11 @@ int Wiimote::GetIndex() const
return m_index;
}
void Wiimote::SetChannel(u16 channel)
{
m_channel = channel;
}
void LoadSettings()
{
std::string ini_filename = File::GetUserPath(D_CONFIG_IDX) + WIIMOTE_INI_NAME ".ini";
@ -763,8 +850,7 @@ void Initialize(::Wiimote::InitializeMode init_mode)
g_wiimote_scanner.StartThread();
}
if (SConfig::GetInstance().m_WiimoteContinuousScanning &&
!SConfig::GetInstance().m_bt_passthrough_enabled)
if (SConfig::GetInstance().m_WiimoteContinuousScanning)
g_wiimote_scanner.SetScanMode(WiimoteScanMode::CONTINUOUSLY_SCAN);
else
g_wiimote_scanner.SetScanMode(WiimoteScanMode::DO_NOT_SCAN);
@ -774,7 +860,7 @@ void Initialize(::Wiimote::InitializeMode init_mode)
{
int timeout = 100;
g_wiimote_scanner.SetScanMode(WiimoteScanMode::SCAN_ONCE);
while (CalculateWantedWiimotes() > CalculateConnectedWiimotes() && timeout)
while (CalculateWantedWiimotes() && timeout)
{
Common::SleepCurrentThread(100);
timeout--;
@ -805,9 +891,13 @@ void Shutdown()
NOTICE_LOG(WIIMOTE, "WiimoteReal::Shutdown");
std::lock_guard<std::mutex> lk(g_wiimotes_mutex);
std::lock_guard lk(g_wiimotes_mutex);
for (unsigned int i = 0; i < MAX_BBMOTES; ++i)
HandleWiimoteDisconnect(i);
// Release remotes from ControllerInterface and empty the pool.
ciface::Wiimote::ReleaseDevices();
g_wiimote_pool.clear();
}
void Resume()
@ -836,6 +926,13 @@ static bool TryToConnectWiimoteToSlot(std::unique_ptr<Wiimote>& wm, unsigned int
return false;
}
wm->Prepare();
// Set LEDs.
OutputReportLeds led_report = {};
led_report.leds = u8(1 << (i % WIIMOTE_BALANCE_BOARD));
wm->QueueReport(led_report);
g_wiimotes[i] = std::move(wm);
Core::RunAsCPUThread([i] { ::Wiimote::Connect(i, true); });
@ -844,22 +941,6 @@ static bool TryToConnectWiimoteToSlot(std::unique_ptr<Wiimote>& wm, unsigned int
return true;
}
static void TryToConnectWiimote(std::unique_ptr<Wiimote> wm)
{
for (unsigned int i = 0; i < MAX_WIIMOTES; ++i)
{
if (TryToConnectWiimoteToSlot(wm, i))
return;
}
INFO_LOG(WIIMOTE, "No open slot for real wiimote, adding it to the pool.");
wm->Connect(0);
// Turn on LED 1 and 4 to make it apparant this remote is in the pool.
const u8 led_value = u8(LED::LED_1) | u8(LED::LED_4);
wm->QueueReport(OutputReportID::LED, &led_value, 1);
g_wiimote_pool.emplace_back(WiimotePoolEntry{std::move(wm)});
}
static void TryToConnectBalanceBoard(std::unique_ptr<Wiimote> wm)
{
if (TryToConnectWiimoteToSlot(wm, WIIMOTE_BALANCE_BOARD))
@ -882,14 +963,14 @@ void Refresh()
void InterruptChannel(int wiimote_number, u16 channel_id, const void* data, u32 size)
{
std::lock_guard<std::mutex> lk(g_wiimotes_mutex);
std::lock_guard lk(g_wiimotes_mutex);
if (g_wiimotes[wiimote_number])
g_wiimotes[wiimote_number]->InterruptChannel(channel_id, data, size);
}
void ControlChannel(int wiimote_number, u16 channel_id, const void* data, u32 size)
{
std::lock_guard<std::mutex> lk(g_wiimotes_mutex);
std::lock_guard lk(g_wiimotes_mutex);
if (g_wiimotes[wiimote_number])
g_wiimotes[wiimote_number]->ControlChannel(channel_id, data, size);
}
@ -946,25 +1027,17 @@ bool IsNewWiimote(const std::string& identifier)
void HandleWiimoteSourceChange(unsigned int index)
{
std::lock_guard<std::mutex> wm_lk(g_wiimotes_mutex);
std::lock_guard wm_lk(g_wiimotes_mutex);
if (WiimoteCommon::GetSource(index) != WiimoteSource::Real)
{
if (auto removed_wiimote = std::move(g_wiimotes[index]))
{
removed_wiimote->EmuStop();
// Try to use this removed wiimote in another slot.
TryToConnectWiimote(std::move(removed_wiimote));
}
}
else if (WiimoteCommon::GetSource(index) == WiimoteSource::Real)
{
// Try to fill this slot from the pool.
if (!g_wiimote_pool.empty())
{
if (TryToConnectWiimoteToSlot(g_wiimote_pool.front().wiimote, index))
g_wiimote_pool.erase(g_wiimote_pool.begin());
}
}
if (auto removed_wiimote = std::move(g_wiimotes[index]))
AddWiimoteToPool(std::move(removed_wiimote));
ProcessWiimotePool();
}
}; // namespace WiimoteReal
void HandleWiimotesInControllerInterfaceSettingChange()
{
ProcessWiimotePool();
}
} // namespace WiimoteReal

View File

@ -65,6 +65,7 @@ public:
void Update();
bool CheckForButtonPress();
bool GetNextReport(Report* report);
Report& ProcessReadQueue();
void Read();
@ -101,8 +102,16 @@ public:
void QueueReport(WiimoteCommon::OutputReportID rpt_id, const void* data, unsigned int size);
template <typename T>
void QueueReport(const T& report)
{
QueueReport(report.REPORT_ID, &report, sizeof(report));
}
int GetIndex() const;
void SetChannel(u16 channel);
protected:
Wiimote();
@ -173,6 +182,7 @@ public:
private:
void ThreadFunc();
void PoolThreadFunc();
std::vector<std::unique_ptr<WiimoteScannerBackend>> m_backends;
mutable std::mutex m_backends_mutex;
@ -183,10 +193,13 @@ private:
std::atomic<WiimoteScanMode> m_scan_mode{WiimoteScanMode::DO_NOT_SCAN};
};
extern std::mutex g_wiimotes_mutex;
// Mutex is recursive as ControllerInterface may call AddWiimoteToPool within ProcessWiimotePool.
extern std::recursive_mutex g_wiimotes_mutex;
extern WiimoteScanner g_wiimote_scanner;
extern std::unique_ptr<Wiimote> g_wiimotes[MAX_BBMOTES];
void AddWiimoteToPool(std::unique_ptr<Wiimote>);
void InterruptChannel(int wiimote_number, u16 channel_id, const void* data, u32 size);
void ControlChannel(int wiimote_number, u16 channel_id, const void* data, u32 size);
void Update(int wiimote_number);
@ -202,4 +215,7 @@ void HandleWiimoteSourceChange(unsigned int wiimote_number);
void InitAdapterClass();
#endif
void HandleWiimotesInControllerInterfaceSettingChange();
void ProcessWiimotePool();
} // namespace WiimoteReal

View File

@ -678,12 +678,13 @@ static void SetWiiInputDisplayString(int remoteID, const DataReportBuilder& rpt,
if (rpt.HasAccel())
{
DataReportBuilder::AccelData accel_data;
AccelData accel_data;
rpt.GetAccelData(&accel_data);
// FYI: This will only print partial data for interleaved reports.
display_str += fmt::format(" ACC:{},{},{}", accel_data.x, accel_data.y, accel_data.z);
display_str +=
fmt::format(" ACC:{},{},{}", accel_data.value.x, accel_data.value.y, accel_data.value.z);
}
if (rpt.HasIR())
@ -707,9 +708,8 @@ static void SetWiiInputDisplayString(int remoteID, const DataReportBuilder& rpt,
key.Decrypt((u8*)&nunchuk, 0, sizeof(nunchuk));
nunchuk.bt.hex = nunchuk.bt.hex ^ 0x3;
const std::string accel = fmt::format(
" N-ACC:{},{},{}", (nunchuk.ax << 2) | nunchuk.bt.acc_x_lsb,
(nunchuk.ay << 2) | nunchuk.bt.acc_y_lsb, (nunchuk.az << 2) | nunchuk.bt.acc_z_lsb);
const std::string accel = fmt::format(" N-ACC:{},{},{}", nunchuk.GetAccelX(),
nunchuk.GetAccelY(), nunchuk.GetAccelZ());
if (nunchuk.bt.c)
display_str += " C";
@ -756,10 +756,14 @@ static void SetWiiInputDisplayString(int remoteID, const DataReportBuilder& rpt,
if (cc.bt.home)
display_str += " HOME";
display_str += Analog1DToString(cc.lt1 | (cc.lt2 << 3), " L", 31);
display_str += Analog1DToString(cc.rt, " R", 31);
display_str += Analog2DToString(cc.lx, cc.ly, " ANA", 63);
display_str += Analog2DToString(cc.rx1 | (cc.rx2 << 1) | (cc.rx3 << 3), cc.ry, " R-ANA", 31);
display_str += Analog1DToString(cc.GetLeftTrigger().value, " L", 31);
display_str += Analog1DToString(cc.GetRightTrigger().value, " R", 31);
const auto left_stick = cc.GetLeftStick().value;
display_str += Analog2DToString(left_stick.x, left_stick.y, " ANA", 63);
const auto right_stick = cc.GetRightStick().value;
display_str += Analog2DToString(right_stick.x, right_stick.y, " R-ANA", 31);
}
std::lock_guard<std::mutex> guard(s_input_display_lock);

View File

@ -72,8 +72,6 @@ ControllersWindow::ControllersWindow(QWidget* parent) : QDialog(parent)
CreateMainLayout();
LoadSettings();
ConnectWidgets();
OnEmulationStateChanged(Core::GetState() != Core::State::Uninitialized);
}
void ControllersWindow::CreateGamecubeLayout()
@ -157,6 +155,7 @@ void ControllersWindow::CreateWiimoteLayout()
m_wiimote_continuous_scanning = new QCheckBox(tr("Continuous Scanning"));
m_wiimote_real_balance_board = new QCheckBox(tr("Real Balance Board"));
m_wiimote_speaker_data = new QCheckBox(tr("Enable Speaker Data"));
m_wiimote_ciface = new QCheckBox(tr("Connect Wii Remotes for Emulated Controllers"));
m_wiimote_layout->setVerticalSpacing(7);
m_wiimote_layout->setColumnMinimumWidth(0, GetRadioButtonIndicatorWidth() -
@ -192,12 +191,14 @@ void ControllersWindow::CreateWiimoteLayout()
m_wiimote_layout->addWidget(wm_button, wm_row, 3);
}
int continuous_scanning_row = m_wiimote_layout->rowCount();
m_wiimote_layout->addWidget(m_wiimote_continuous_scanning, continuous_scanning_row, 1, 1, 2);
m_wiimote_layout->addWidget(m_wiimote_refresh, continuous_scanning_row, 3);
m_wiimote_layout->addWidget(m_wiimote_real_balance_board, m_wiimote_layout->rowCount(), 1, 1, -1);
m_wiimote_layout->addWidget(m_wiimote_speaker_data, m_wiimote_layout->rowCount(), 1, 1, -1);
m_wiimote_layout->addWidget(m_wiimote_ciface, m_wiimote_layout->rowCount(), 0, 1, -1);
int continuous_scanning_row = m_wiimote_layout->rowCount();
m_wiimote_layout->addWidget(m_wiimote_continuous_scanning, continuous_scanning_row, 0, 1, 3);
m_wiimote_layout->addWidget(m_wiimote_refresh, continuous_scanning_row, 3);
}
void ControllersWindow::CreateCommonLayout()
@ -232,10 +233,15 @@ void ControllersWindow::ConnectWidgets()
{
connect(m_button_box, &QDialogButtonBox::rejected, this, &QDialog::reject);
connect(&Settings::Instance(), &Settings::EmulationStateChanged, this,
[=](Core::State state) { OnEmulationStateChanged(state != Core::State::Uninitialized); });
&ControllersWindow::UpdateDisabledWiimoteControls);
connect(m_wiimote_passthrough, &QRadioButton::toggled, this,
&ControllersWindow::OnWiimoteModeChanged);
connect(m_wiimote_ciface, &QCheckBox::toggled, this, &ControllersWindow::OnWiimoteModeChanged);
connect(m_wiimote_ciface, &QCheckBox::toggled, this,
&WiimoteReal::HandleWiimotesInControllerInterfaceSettingChange);
connect(m_wiimote_continuous_scanning, &QCheckBox::toggled, this,
&ControllersWindow::OnWiimoteModeChanged);
connect(m_common_bg_input, &QCheckBox::toggled, this, &ControllersWindow::SaveSettings);
connect(m_common_configure_controller_interface, &QPushButton::clicked, this,
@ -259,7 +265,7 @@ void ControllersWindow::ConnectWidgets()
&ControllersWindow::SaveSettings);
connect(m_wiimote_boxes[i],
static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
&ControllersWindow::OnWiimoteTypeChanged);
&ControllersWindow::OnWiimoteModeChanged);
connect(m_wiimote_buttons[i], &QPushButton::clicked, this,
&ControllersWindow::OnWiimoteConfigure);
@ -273,45 +279,50 @@ void ControllersWindow::ConnectWidgets()
}
}
void ControllersWindow::OnWiimoteModeChanged(bool passthrough)
void ControllersWindow::OnWiimoteModeChanged()
{
SaveSettings();
m_wiimote_sync->setEnabled(passthrough);
m_wiimote_reset->setEnabled(passthrough);
// Make sure continuous scanning setting is applied.
WiimoteReal::Initialize(::Wiimote::InitializeMode::DO_NOT_WAIT_FOR_WIIMOTES);
for (size_t i = 0; i < m_wiimote_groups.size(); i++)
{
const int index = m_wiimote_boxes[i]->currentIndex();
if (i < 2)
m_wiimote_pt_labels[i]->setEnabled(passthrough);
m_wiimote_labels[i]->setEnabled(!passthrough);
m_wiimote_boxes[i]->setEnabled(!passthrough);
m_wiimote_buttons[i]->setEnabled(!passthrough && index != 0 && index != 2);
}
m_wiimote_refresh->setEnabled(!passthrough);
m_wiimote_real_balance_board->setEnabled(!passthrough);
m_wiimote_speaker_data->setEnabled(!passthrough);
m_wiimote_continuous_scanning->setEnabled(!passthrough);
UpdateDisabledWiimoteControls();
}
void ControllersWindow::OnWiimoteTypeChanged(int type)
void ControllersWindow::UpdateDisabledWiimoteControls()
{
const auto* box = static_cast<QComboBox*>(QObject::sender());
const bool running = Core::GetState() != Core::State::Uninitialized;
m_wiimote_emu->setEnabled(!running);
m_wiimote_passthrough->setEnabled(!running);
const bool running_gc = running && !SConfig::GetInstance().bWii;
const bool enable_passthrough = m_wiimote_passthrough->isChecked() && !running_gc;
const bool enable_emu_bt = !m_wiimote_passthrough->isChecked() && !running_gc;
m_wiimote_sync->setEnabled(enable_passthrough);
m_wiimote_reset->setEnabled(enable_passthrough);
for (auto* pt_label : m_wiimote_pt_labels)
pt_label->setEnabled(enable_passthrough);
for (size_t i = 0; i < m_wiimote_groups.size(); i++)
{
if (m_wiimote_boxes[i] == box)
{
const int index = box->currentIndex();
m_wiimote_buttons[i]->setEnabled(index != 0 && index != 2);
return;
}
m_wiimote_labels[i]->setEnabled(enable_emu_bt);
m_wiimote_boxes[i]->setEnabled(enable_emu_bt);
const bool is_emu_wiimote = m_wiimote_boxes[i]->currentIndex() == 1;
m_wiimote_buttons[i]->setEnabled(enable_emu_bt && is_emu_wiimote);
}
SaveSettings();
m_wiimote_real_balance_board->setEnabled(enable_emu_bt);
m_wiimote_speaker_data->setEnabled(enable_emu_bt);
const bool ciface_wiimotes = m_wiimote_ciface->isChecked();
m_wiimote_refresh->setEnabled((enable_emu_bt || ciface_wiimotes) &&
!m_wiimote_continuous_scanning->isChecked());
m_wiimote_continuous_scanning->setEnabled(enable_emu_bt || ciface_wiimotes);
}
void ControllersWindow::OnGCTypeChanged(int type)
@ -375,30 +386,6 @@ void ControllersWindow::OnWiimoteRefreshPressed()
WiimoteReal::Refresh();
}
void ControllersWindow::OnEmulationStateChanged(bool running)
{
const bool passthrough = SConfig::GetInstance().m_bt_passthrough_enabled;
if (!SConfig::GetInstance().bWii)
{
m_wiimote_sync->setEnabled(!running && passthrough);
m_wiimote_reset->setEnabled(!running && passthrough);
for (size_t i = 0; i < m_wiimote_groups.size(); i++)
m_wiimote_boxes[i]->setEnabled(!running && !passthrough);
}
m_wiimote_emu->setEnabled(!running);
m_wiimote_passthrough->setEnabled(!running);
if (!SConfig::GetInstance().bWii)
{
m_wiimote_real_balance_board->setEnabled(!running && !passthrough);
m_wiimote_continuous_scanning->setEnabled(!running && !passthrough);
m_wiimote_speaker_data->setEnabled(!running && !passthrough);
}
}
void ControllersWindow::OnGCPadConfigure()
{
size_t index;
@ -489,14 +476,12 @@ void ControllersWindow::LoadSettings()
m_gc_controller_boxes[i]->setCurrentIndex(*gc_index);
m_gc_buttons[i]->setEnabled(*gc_index != 0 && *gc_index != 6);
}
const WiimoteSource source = WiimoteCommon::GetSource(int(i));
m_wiimote_boxes[i]->setCurrentIndex(int(source));
m_wiimote_buttons[i]->setEnabled(source == WiimoteSource::Emulated);
m_wiimote_boxes[i]->setCurrentIndex(int(WiimoteCommon::GetSource(u32(i))));
}
m_wiimote_real_balance_board->setChecked(WiimoteCommon::GetSource(WIIMOTE_BALANCE_BOARD) ==
WiimoteSource::Real);
m_wiimote_speaker_data->setChecked(SConfig::GetInstance().m_WiimoteEnableSpeaker);
m_wiimote_ciface->setChecked(SConfig::GetInstance().connect_wiimotes_for_ciface);
m_wiimote_continuous_scanning->setChecked(SConfig::GetInstance().m_WiimoteContinuousScanning);
m_common_bg_input->setChecked(SConfig::GetInstance().m_BackgroundInput);
@ -506,12 +491,13 @@ void ControllersWindow::LoadSettings()
else
m_wiimote_emu->setChecked(true);
OnWiimoteModeChanged(SConfig::GetInstance().m_bt_passthrough_enabled);
OnWiimoteModeChanged();
}
void ControllersWindow::SaveSettings()
{
SConfig::GetInstance().m_WiimoteEnableSpeaker = m_wiimote_speaker_data->isChecked();
SConfig::GetInstance().connect_wiimotes_for_ciface = m_wiimote_ciface->isChecked();
SConfig::GetInstance().m_WiimoteContinuousScanning = m_wiimote_continuous_scanning->isChecked();
SConfig::GetInstance().m_bt_passthrough_enabled = m_wiimote_passthrough->isChecked();
SConfig::GetInstance().m_BackgroundInput = m_common_bg_input->isChecked();
@ -522,9 +508,8 @@ void ControllersWindow::SaveSettings()
for (size_t i = 0; i < m_wiimote_groups.size(); i++)
{
const auto source = WiimoteSource(m_wiimote_boxes[i]->currentIndex());
m_wiimote_buttons[i]->setEnabled(source == WiimoteSource::Emulated);
WiimoteCommon::SetSource(static_cast<u32>(i), source);
const int index = m_wiimote_boxes[i]->currentIndex();
WiimoteCommon::SetSource(u32(i), WiimoteSource(index));
}
UICommon::SaveWiimoteSources();

View File

@ -27,9 +27,8 @@ public:
explicit ControllersWindow(QWidget* parent);
private:
void OnEmulationStateChanged(bool running);
void OnWiimoteModeChanged(bool passthrough);
void OnWiimoteTypeChanged(int state);
void OnWiimoteModeChanged();
void UpdateDisabledWiimoteControls();
void OnGCTypeChanged(int state);
void SaveSettings();
void OnBluetoothPassthroughSyncPressed();
@ -72,6 +71,7 @@ private:
QCheckBox* m_wiimote_continuous_scanning;
QCheckBox* m_wiimote_real_balance_board;
QCheckBox* m_wiimote_speaker_data;
QCheckBox* m_wiimote_ciface;
QPushButton* m_wiimote_refresh;
// Common

View File

@ -159,10 +159,12 @@ bool IsCalibrationDataSensible(const ControllerEmu::ReshapableInput::Calibration
// Even the GC controller's small range would pass this test.
constexpr double REASONABLE_AVERAGE_RADIUS = 0.6;
const double sum = std::accumulate(data.begin(), data.end(), 0.0);
const double mean = sum / data.size();
MathUtil::RunningVariance<ControlState> stats;
if (mean < REASONABLE_AVERAGE_RADIUS)
for (auto& x : data)
stats.Push(x);
if (stats.Mean() < REASONABLE_AVERAGE_RADIUS)
{
return false;
}
@ -173,11 +175,7 @@ bool IsCalibrationDataSensible(const ControllerEmu::ReshapableInput::Calibration
// Approx. deviation of a square input gate, anything much more than that would be unusual.
constexpr double REASONABLE_DEVIATION = 0.14;
// Population standard deviation.
const double square_sum = std::inner_product(data.begin(), data.end(), data.begin(), 0.0);
const double standard_deviation = std::sqrt(square_sum / data.size() - mean * mean);
return standard_deviation < REASONABLE_DEVIATION;
return stats.StandardDeviation() < REASONABLE_DEVIATION;
}
// Used to test for a miscalibrated stick so the user can be informed.
@ -754,33 +752,34 @@ void AccelerometerMappingIndicator::paintEvent(QPaintEvent*)
p.setBrush(Qt::NoBrush);
p.drawEllipse(QPointF{}, scale * SPHERE_SIZE, scale * SPHERE_SIZE);
// Red dot upright target.
p.setPen(QPen(GetAdjustedInputColor(), INPUT_DOT_RADIUS / 2));
p.drawEllipse(QPointF{0, SPHERE_INDICATOR_DIST} * scale, INPUT_DOT_RADIUS, INPUT_DOT_RADIUS);
p.setPen(Qt::NoPen);
// Red dot.
const auto point = rotation * Common::Vec3{0, 0, SPHERE_INDICATOR_DIST};
if (point.y > 0 || Common::Vec2(point.x, point.z).Length() > SPHERE_SIZE)
{
p.setPen(Qt::NoPen);
p.setBrush(GetAdjustedInputColor());
p.drawEllipse(QPointF(point.x, point.z) * scale, INPUT_DOT_RADIUS, INPUT_DOT_RADIUS);
}
// Blue dot target.
p.setPen(QPen(Qt::blue, INPUT_DOT_RADIUS / 2));
p.setBrush(Qt::NoBrush);
p.drawEllipse(QPointF{0, -SPHERE_INDICATOR_DIST} * scale, INPUT_DOT_RADIUS, INPUT_DOT_RADIUS);
// Blue dot.
const auto point2 = -point;
if (point2.y > 0 || Common::Vec2(point2.x, point2.z).Length() > SPHERE_SIZE)
{
p.setPen(Qt::NoPen);
p.setBrush(Qt::blue);
p.drawEllipse(QPointF(point2.x, point2.z) * scale, INPUT_DOT_RADIUS, INPUT_DOT_RADIUS);
}
p.setBrush(Qt::NoBrush);
// Red dot upright target.
p.setPen(QPen(GetAdjustedInputColor(), INPUT_DOT_RADIUS / 2));
p.drawEllipse(QPointF{0, SPHERE_INDICATOR_DIST} * scale, INPUT_DOT_RADIUS, INPUT_DOT_RADIUS);
// Blue dot target.
p.setPen(QPen(Qt::blue, INPUT_DOT_RADIUS / 2));
p.drawEllipse(QPointF{0, -SPHERE_INDICATOR_DIST} * scale, INPUT_DOT_RADIUS, INPUT_DOT_RADIUS);
// Only draw g-force text if acceleration data is present.
if (!accel_state.has_value())
return;
@ -802,16 +801,18 @@ GyroMappingIndicator::GyroMappingIndicator(ControllerEmu::IMUGyroscope* group)
void GyroMappingIndicator::paintEvent(QPaintEvent*)
{
const auto gyro_state = m_gyro_group.GetState();
const auto raw_gyro_state = m_gyro_group.GetRawState();
const auto angular_velocity = gyro_state.value_or(Common::Vec3{});
const auto jitter = raw_gyro_state - m_previous_velocity;
m_previous_velocity = raw_gyro_state;
m_state *= Common::Matrix33::FromQuaternion(angular_velocity.x / -INDICATOR_UPDATE_FREQ / 2,
angular_velocity.y / INDICATOR_UPDATE_FREQ / 2,
angular_velocity.z / -INDICATOR_UPDATE_FREQ / 2, 1);
m_state *= WiimoteEmu::GetMatrixFromGyroscope(angular_velocity * Common::Vec3(-1, +1, -1) /
INDICATOR_UPDATE_FREQ);
// Reset orientation when stable for a bit:
constexpr u32 STABLE_RESET_STEPS = INDICATOR_UPDATE_FREQ;
// This works well with my DS4 but a potentially noisy device might not behave.
const bool is_stable = angular_velocity.Length() < MathUtil::TAU / 30;
// Consider device stable when data (with deadzone applied) is zero.
const bool is_stable = !angular_velocity.LengthSquared();
if (!is_stable)
m_stable_steps = 0;
@ -839,10 +840,39 @@ void GyroMappingIndicator::paintEvent(QPaintEvent*)
p.setRenderHint(QPainter::Antialiasing, true);
p.setRenderHint(QPainter::SmoothPixmapTransform, true);
// Deadzone.
if (const auto deadzone_value = m_gyro_group.GetDeadzone(); deadzone_value)
{
static constexpr auto DEADZONE_DRAW_SIZE = 1 - SPHERE_SIZE;
static constexpr auto DEADZONE_DRAW_BOTTOM = 1;
p.setPen(GetDeadZonePen());
p.setBrush(GetDeadZoneBrush());
p.scale(-1.0, 1.0);
p.drawRect(-scale, DEADZONE_DRAW_BOTTOM * scale, scale * 2, -scale * DEADZONE_DRAW_SIZE);
p.scale(-1.0, 1.0);
if (gyro_state.has_value())
{
const auto max_jitter =
std::max({std::abs(jitter.x), std::abs(jitter.y), std::abs(jitter.z)});
const auto jitter_line_y =
std::min(max_jitter / deadzone_value * DEADZONE_DRAW_SIZE - DEADZONE_DRAW_BOTTOM, 1.0);
p.setPen(QPen(GetRawInputColor(), INPUT_DOT_RADIUS));
p.drawLine(-scale, jitter_line_y * -scale, scale, jitter_line_y * -scale);
// Sphere background.
p.setPen(Qt::NoPen);
p.setBrush(GetBBoxBrush());
p.drawEllipse(QPointF{}, scale * SPHERE_SIZE, scale * SPHERE_SIZE);
}
}
// Sphere dots.
p.setPen(Qt::NoPen);
p.setBrush(GetRawInputColor());
GenerateFibonacciSphere(SPHERE_POINT_COUNT, [&, this](const Common::Vec3& point) {
GenerateFibonacciSphere(SPHERE_POINT_COUNT, [&](const Common::Vec3& point) {
const auto pt = rotation * point;
if (pt.y > 0)
@ -850,49 +880,39 @@ void GyroMappingIndicator::paintEvent(QPaintEvent*)
});
// Sphere outline.
p.setPen(GetRawInputColor());
const auto outline_color = is_stable ?
(m_gyro_group.IsCalibrating() ? Qt::blue : GetRawInputColor()) :
GetAdjustedInputColor();
p.setPen(outline_color);
p.setBrush(Qt::NoBrush);
p.drawEllipse(QPointF{}, scale * SPHERE_SIZE, scale * SPHERE_SIZE);
// Red dot upright target.
p.setPen(QPen(GetAdjustedInputColor(), INPUT_DOT_RADIUS / 2));
p.drawEllipse(QPointF{0, -SPHERE_INDICATOR_DIST} * scale, INPUT_DOT_RADIUS, INPUT_DOT_RADIUS);
p.setPen(Qt::NoPen);
// Red dot.
const auto point = rotation * Common::Vec3{0, 0, -SPHERE_INDICATOR_DIST};
if (point.y > 0 || Common::Vec2(point.x, point.z).Length() > SPHERE_SIZE)
{
p.setPen(Qt::NoPen);
p.setBrush(GetAdjustedInputColor());
p.drawEllipse(QPointF(point.x, point.z) * scale, INPUT_DOT_RADIUS, INPUT_DOT_RADIUS);
}
// Blue dot target.
p.setPen(QPen(Qt::blue, INPUT_DOT_RADIUS / 2));
p.setBrush(Qt::NoBrush);
p.drawEllipse(QPointF{}, INPUT_DOT_RADIUS, INPUT_DOT_RADIUS);
// Blue dot.
const auto point2 = rotation * Common::Vec3{0, SPHERE_INDICATOR_DIST, 0};
if (point2.y > 0 || Common::Vec2(point2.x, point2.z).Length() > SPHERE_SIZE)
{
p.setPen(Qt::NoPen);
p.setBrush(Qt::blue);
p.drawEllipse(QPointF(point2.x, point2.z) * scale, INPUT_DOT_RADIUS, INPUT_DOT_RADIUS);
}
// Only draw text if data is present.
if (!gyro_state.has_value())
return;
p.setBrush(Qt::NoBrush);
// Angle of red dot from starting position.
const auto angle = std::acos(point.Normalized().Dot({0, 0, -1}));
// Red dot upright target.
p.setPen(QPen(GetAdjustedInputColor(), INPUT_DOT_RADIUS / 2));
p.drawEllipse(QPointF{0, -SPHERE_INDICATOR_DIST} * scale, INPUT_DOT_RADIUS, INPUT_DOT_RADIUS);
// Angle text:
p.setPen(GetTextColor());
p.drawText(QRectF(-2, 0, scale, scale), Qt::AlignBottom | Qt::AlignRight,
// i18n: "°" is the symbol for degrees (angular measurement).
QString::fromStdString(fmt::format("{:.2f} °", angle / MathUtil::TAU * 360)));
// Blue dot target.
p.setPen(QPen(Qt::blue, INPUT_DOT_RADIUS / 2));
p.drawEllipse(QPointF{}, INPUT_DOT_RADIUS, INPUT_DOT_RADIUS);
}
void MappingIndicator::DrawCalibration(QPainter& p, Common::DVec2 point)

View File

@ -101,6 +101,7 @@ public:
private:
ControllerEmu::IMUGyroscope& m_gyro_group;
Common::Matrix33 m_state;
Common::Vec3 m_previous_velocity = {};
u32 m_stable_steps = 0;
};

View File

@ -334,18 +334,20 @@ void WiiTASInputWindow::GetValues(DataReportBuilder& rpt, int ext,
DataReportBuilder::CoreData core;
rpt.GetCoreData(&core);
using EmuWiimote = WiimoteEmu::Wiimote;
u16& buttons = core.hex;
GetButton<u16>(m_a_button, buttons, WiimoteEmu::Wiimote::BUTTON_A);
GetButton<u16>(m_b_button, buttons, WiimoteEmu::Wiimote::BUTTON_B);
GetButton<u16>(m_1_button, buttons, WiimoteEmu::Wiimote::BUTTON_ONE);
GetButton<u16>(m_2_button, buttons, WiimoteEmu::Wiimote::BUTTON_TWO);
GetButton<u16>(m_plus_button, buttons, WiimoteEmu::Wiimote::BUTTON_PLUS);
GetButton<u16>(m_minus_button, buttons, WiimoteEmu::Wiimote::BUTTON_MINUS);
GetButton<u16>(m_home_button, buttons, WiimoteEmu::Wiimote::BUTTON_HOME);
GetButton<u16>(m_left_button, buttons, WiimoteEmu::Wiimote::PAD_LEFT);
GetButton<u16>(m_up_button, buttons, WiimoteEmu::Wiimote::PAD_UP);
GetButton<u16>(m_down_button, buttons, WiimoteEmu::Wiimote::PAD_DOWN);
GetButton<u16>(m_right_button, buttons, WiimoteEmu::Wiimote::PAD_RIGHT);
GetButton<u16>(m_a_button, buttons, EmuWiimote::BUTTON_A);
GetButton<u16>(m_b_button, buttons, EmuWiimote::BUTTON_B);
GetButton<u16>(m_1_button, buttons, EmuWiimote::BUTTON_ONE);
GetButton<u16>(m_2_button, buttons, EmuWiimote::BUTTON_TWO);
GetButton<u16>(m_plus_button, buttons, EmuWiimote::BUTTON_PLUS);
GetButton<u16>(m_minus_button, buttons, EmuWiimote::BUTTON_MINUS);
GetButton<u16>(m_home_button, buttons, EmuWiimote::BUTTON_HOME);
GetButton<u16>(m_left_button, buttons, EmuWiimote::PAD_LEFT);
GetButton<u16>(m_up_button, buttons, EmuWiimote::PAD_UP);
GetButton<u16>(m_down_button, buttons, EmuWiimote::PAD_DOWN);
GetButton<u16>(m_right_button, buttons, EmuWiimote::PAD_RIGHT);
rpt.SetCoreData(core);
}
@ -354,12 +356,12 @@ void WiiTASInputWindow::GetValues(DataReportBuilder& rpt, int ext,
{
// FYI: Interleaved reports may behave funky as not all data is always available.
DataReportBuilder::AccelData accel;
AccelData accel;
rpt.GetAccelData(&accel);
GetSpinBoxU16(m_remote_orientation_x_value, accel.x);
GetSpinBoxU16(m_remote_orientation_y_value, accel.y);
GetSpinBoxU16(m_remote_orientation_z_value, accel.z);
GetSpinBoxU16(m_remote_orientation_x_value, accel.value.x);
GetSpinBoxU16(m_remote_orientation_y_value, accel.value.y);
GetSpinBoxU16(m_remote_orientation_z_value, accel.value.z);
rpt.SetAccelData(accel);
}
@ -439,26 +441,16 @@ void WiiTASInputWindow::GetValues(DataReportBuilder& rpt, int ext,
GetSpinBoxU8(m_nunchuk_stick_x_value, nunchuk.jx);
GetSpinBoxU8(m_nunchuk_stick_y_value, nunchuk.jy);
u16 accel_x = nunchuk.ax << 2 & (nunchuk.bt.acc_x_lsb & 0b11);
u16 accel_y = nunchuk.ay << 2 & (nunchuk.bt.acc_y_lsb & 0b11);
u16 accel_z = nunchuk.az << 2 & (nunchuk.bt.acc_z_lsb & 0b11);
auto accel = nunchuk.GetAccel().value;
GetSpinBoxU16(m_nunchuk_orientation_x_value, accel.x);
GetSpinBoxU16(m_nunchuk_orientation_y_value, accel.y);
GetSpinBoxU16(m_nunchuk_orientation_z_value, accel.z);
nunchuk.SetAccel(accel);
GetSpinBoxU16(m_nunchuk_orientation_x_value, accel_x);
GetSpinBoxU16(m_nunchuk_orientation_y_value, accel_y);
GetSpinBoxU16(m_nunchuk_orientation_z_value, accel_z);
nunchuk.ax = accel_x >> 2;
nunchuk.ay = accel_y >> 2;
nunchuk.az = accel_z >> 2;
nunchuk.bt.acc_x_lsb = accel_x & 0b11;
nunchuk.bt.acc_y_lsb = accel_y & 0b11;
nunchuk.bt.acc_z_lsb = accel_z & 0b11;
nunchuk.bt.hex ^= 0b11;
GetButton<u8>(m_c_button, nunchuk.bt.hex, WiimoteEmu::Nunchuk::BUTTON_C);
GetButton<u8>(m_z_button, nunchuk.bt.hex, WiimoteEmu::Nunchuk::BUTTON_Z);
nunchuk.bt.hex ^= 0b11;
u8 bt = nunchuk.GetButtons();
GetButton<u8>(m_c_button, bt, WiimoteEmu::Nunchuk::BUTTON_C);
GetButton<u8>(m_z_button, bt, WiimoteEmu::Nunchuk::BUTTON_Z);
nunchuk.SetButtons(bt);
key.Encrypt(reinterpret_cast<u8*>(&nunchuk), 0, sizeof(nunchuk));
}
@ -470,50 +462,41 @@ void WiiTASInputWindow::GetValues(DataReportBuilder& rpt, int ext,
auto& cc = *reinterpret_cast<WiimoteEmu::Classic::DataFormat*>(ext_data);
key.Decrypt(reinterpret_cast<u8*>(&cc), 0, sizeof(cc));
cc.bt.hex ^= 0xFFFF;
GetButton<u16>(m_classic_a_button, cc.bt.hex, WiimoteEmu::Classic::BUTTON_A);
GetButton<u16>(m_classic_b_button, cc.bt.hex, WiimoteEmu::Classic::BUTTON_B);
GetButton<u16>(m_classic_x_button, cc.bt.hex, WiimoteEmu::Classic::BUTTON_X);
GetButton<u16>(m_classic_y_button, cc.bt.hex, WiimoteEmu::Classic::BUTTON_Y);
GetButton<u16>(m_classic_plus_button, cc.bt.hex, WiimoteEmu::Classic::BUTTON_PLUS);
GetButton<u16>(m_classic_minus_button, cc.bt.hex, WiimoteEmu::Classic::BUTTON_MINUS);
GetButton<u16>(m_classic_l_button, cc.bt.hex, WiimoteEmu::Classic::TRIGGER_L);
GetButton<u16>(m_classic_r_button, cc.bt.hex, WiimoteEmu::Classic::TRIGGER_R);
GetButton<u16>(m_classic_zl_button, cc.bt.hex, WiimoteEmu::Classic::BUTTON_ZL);
GetButton<u16>(m_classic_zr_button, cc.bt.hex, WiimoteEmu::Classic::BUTTON_ZR);
GetButton<u16>(m_classic_home_button, cc.bt.hex, WiimoteEmu::Classic::BUTTON_HOME);
GetButton<u16>(m_classic_left_button, cc.bt.hex, WiimoteEmu::Classic::PAD_LEFT);
GetButton<u16>(m_classic_up_button, cc.bt.hex, WiimoteEmu::Classic::PAD_UP);
GetButton<u16>(m_classic_down_button, cc.bt.hex, WiimoteEmu::Classic::PAD_DOWN);
GetButton<u16>(m_classic_right_button, cc.bt.hex, WiimoteEmu::Classic::PAD_RIGHT);
cc.bt.hex ^= 0xFFFF;
u16 bt = cc.GetButtons();
GetButton<u16>(m_classic_a_button, bt, WiimoteEmu::Classic::BUTTON_A);
GetButton<u16>(m_classic_b_button, bt, WiimoteEmu::Classic::BUTTON_B);
GetButton<u16>(m_classic_x_button, bt, WiimoteEmu::Classic::BUTTON_X);
GetButton<u16>(m_classic_y_button, bt, WiimoteEmu::Classic::BUTTON_Y);
GetButton<u16>(m_classic_plus_button, bt, WiimoteEmu::Classic::BUTTON_PLUS);
GetButton<u16>(m_classic_minus_button, bt, WiimoteEmu::Classic::BUTTON_MINUS);
GetButton<u16>(m_classic_l_button, bt, WiimoteEmu::Classic::TRIGGER_L);
GetButton<u16>(m_classic_r_button, bt, WiimoteEmu::Classic::TRIGGER_R);
GetButton<u16>(m_classic_zl_button, bt, WiimoteEmu::Classic::BUTTON_ZL);
GetButton<u16>(m_classic_zr_button, bt, WiimoteEmu::Classic::BUTTON_ZR);
GetButton<u16>(m_classic_home_button, bt, WiimoteEmu::Classic::BUTTON_HOME);
GetButton<u16>(m_classic_left_button, bt, WiimoteEmu::Classic::PAD_LEFT);
GetButton<u16>(m_classic_up_button, bt, WiimoteEmu::Classic::PAD_UP);
GetButton<u16>(m_classic_down_button, bt, WiimoteEmu::Classic::PAD_DOWN);
GetButton<u16>(m_classic_right_button, bt, WiimoteEmu::Classic::PAD_RIGHT);
cc.SetButtons(bt);
u8 rx = (cc.rx1 & 0b1) & ((cc.rx2 & 0b11) << 1) & ((cc.rx3 & 0b11) << 3);
GetSpinBoxU8(m_classic_right_stick_x_value, rx);
cc.rx1 = rx & 0b1;
cc.rx2 = (rx >> 1) & 0b11;
cc.rx3 = (rx >> 3) & 0b11;
auto right_stick = cc.GetRightStick().value;
GetSpinBoxU8(m_classic_right_stick_x_value, right_stick.x);
GetSpinBoxU8(m_classic_right_stick_y_value, right_stick.y);
cc.SetRightStick(right_stick);
u8 ry = cc.ry;
GetSpinBoxU8(m_classic_right_stick_y_value, ry);
cc.ry = ry;
auto left_stick = cc.GetLeftStick().value;
GetSpinBoxU8(m_classic_left_stick_x_value, left_stick.x);
GetSpinBoxU8(m_classic_left_stick_y_value, left_stick.y);
cc.SetLeftStick(left_stick);
u8 lx = cc.lx;
GetSpinBoxU8(m_classic_left_stick_x_value, lx);
cc.lx = lx;
u8 ly = cc.ly;
GetSpinBoxU8(m_classic_left_stick_y_value, ly);
cc.ly = ly;
u8 rt = cc.rt;
u8 rt = cc.GetRightTrigger().value;
GetSpinBoxU8(m_right_trigger_value, rt);
cc.rt = rt;
cc.SetRightTrigger(rt);
u8 lt = (cc.lt1 & 0b111) & (cc.lt2 >> 3);
u8 lt = cc.GetLeftTrigger().value;
GetSpinBoxU8(m_left_trigger_value, lt);
cc.lt1 = lt & 0b111;
cc.lt2 = (lt >> 3) & 0b11;
cc.SetLeftTrigger(lt);
key.Encrypt(reinterpret_cast<u8*>(&cc), 0, sizeof(cc));
}

View File

@ -50,6 +50,8 @@ add_library(inputcommon
ControllerInterface/ControllerInterface.h
ControllerInterface/Device.cpp
ControllerInterface/Device.h
ControllerInterface/Wiimote/Wiimote.cpp
ControllerInterface/Wiimote/Wiimote.h
ControlReference/ControlReference.cpp
ControlReference/ControlReference.h
ControlReference/ExpressionParser.cpp

View File

@ -50,7 +50,7 @@ Cursor::Cursor(std::string name, std::string ui_name)
_trans("°"),
// i18n: Refers to emulated wii remote movements.
_trans("Total rotation about the yaw axis.")},
15, 0, 180);
15, 0, 360);
AddSetting(&m_pitch_setting,
// i18n: Refers to an amount of rotational movement about the "pitch" axis.
@ -59,7 +59,7 @@ Cursor::Cursor(std::string name, std::string ui_name)
_trans("°"),
// i18n: Refers to emulated wii remote movements.
_trans("Total rotation about the pitch axis.")},
15, 0, 180);
15, 0, 360);
AddSetting(&m_relative_setting, {_trans("Relative Input")}, false);
AddSetting(&m_autohide_setting, {_trans("Auto-Hide")}, false);

View File

@ -7,6 +7,7 @@
#include <memory>
#include "Common/Common.h"
#include "Common/MathUtil.h"
#include "InputCommon/ControlReference/ControlReference.h"
#include "InputCommon/ControllerEmu/Control/Control.h"
@ -14,6 +15,15 @@
namespace ControllerEmu
{
// Maximum period for calculating an average stable value.
// Just to prevent failures due to timer overflow.
static constexpr auto MAXIMUM_CALIBRATION_DURATION = std::chrono::hours(1);
// If calibration updates do not happen at this rate, restart calibration period.
// This prevents calibration across periods of no regular updates. (e.g. between game sessions)
// This is made slightly lower than the UI update frequency of 30.
static constexpr auto WORST_ACCEPTABLE_CALIBRATION_UPDATE_FREQUENCY = 25;
IMUGyroscope::IMUGyroscope(std::string name, std::string ui_name)
: ControlGroup(std::move(name), std::move(ui_name), GroupType::IMUGyroscope)
{
@ -23,18 +33,130 @@ IMUGyroscope::IMUGyroscope(std::string name, std::string ui_name)
AddInput(Translate, _trans("Roll Right"));
AddInput(Translate, _trans("Yaw Left"));
AddInput(Translate, _trans("Yaw Right"));
AddSetting(&m_deadzone_setting,
{_trans("Dead Zone"),
// i18n: "°/s" is the symbol for degrees (angular measurement) divided by seconds.
_trans("°/s"),
// i18n: Refers to the dead-zone setting of gyroscope input.
_trans("Angular velocity to ignore.")},
2, 0, 180);
AddSetting(&m_calibration_period_setting,
{_trans("Calibration Period"),
// i18n: "s" is the symbol for seconds.
_trans("s"),
// i18n: Refers to the "Calibration" setting of gyroscope input.
_trans("Time period of stable input to trigger calibration. (zero to disable)")},
3, 0, 30);
}
void IMUGyroscope::RestartCalibration() const
{
m_calibration_period_start = Clock::now();
m_running_calibration.Clear();
}
void IMUGyroscope::UpdateCalibration(const StateData& state) const
{
const auto now = Clock::now();
const auto calibration_period = m_calibration_period_setting.GetValue();
// If calibration time is zero. User is choosing to not calibrate.
if (!calibration_period)
{
// Set calibration to zero.
m_calibration = {};
RestartCalibration();
return;
}
// If there is no running calibration a new gyro was just mapped or calibration was just enabled,
// apply the current state as calibration, it's often better than zeros.
if (!m_running_calibration.Count())
{
m_calibration = state;
}
else
{
const auto calibration_freq =
m_running_calibration.Count() /
std::chrono::duration_cast<std::chrono::duration<double>>(now - m_calibration_period_start)
.count();
const auto potential_calibration = m_running_calibration.Mean();
const auto current_difference = state - potential_calibration;
const auto deadzone = GetDeadzone();
// Check for required calibration update frequency
// and if current data is within deadzone distance of mean stable value.
if (calibration_freq < WORST_ACCEPTABLE_CALIBRATION_UPDATE_FREQUENCY ||
std::any_of(current_difference.data.begin(), current_difference.data.end(),
[&](auto c) { return std::abs(c) > deadzone; }))
{
RestartCalibration();
}
}
// Update running mean stable value.
m_running_calibration.Push(state);
// Apply calibration after configured time.
const auto calibration_duration = now - m_calibration_period_start;
if (calibration_duration >= std::chrono::duration<double>(calibration_period))
{
m_calibration = m_running_calibration.Mean();
if (calibration_duration >= MAXIMUM_CALIBRATION_DURATION)
{
RestartCalibration();
m_running_calibration.Push(m_calibration);
}
}
}
auto IMUGyroscope::GetRawState() const -> StateData
{
return StateData(controls[1]->GetState() - controls[0]->GetState(),
controls[2]->GetState() - controls[3]->GetState(),
controls[4]->GetState() - controls[5]->GetState());
}
std::optional<IMUGyroscope::StateData> IMUGyroscope::GetState() const
{
if (controls[0]->control_ref->BoundCount() == 0)
{
// Set calibration to zero.
m_calibration = {};
RestartCalibration();
return std::nullopt;
}
auto state = GetRawState();
// If the input gate is disabled, miscalibration to zero values would occur.
if (ControlReference::GetInputGate())
UpdateCalibration(state);
state -= m_calibration;
// Apply "deadzone".
for (auto& c : state.data)
c *= std::abs(c) > GetDeadzone();
StateData state;
state.x = (controls[1]->GetState() - controls[0]->GetState());
state.y = (controls[2]->GetState() - controls[3]->GetState());
state.z = (controls[4]->GetState() - controls[5]->GetState());
return state;
}
ControlState IMUGyroscope::GetDeadzone() const
{
return m_deadzone_setting.GetValue() / 360 * MathUtil::TAU;
}
bool IMUGyroscope::IsCalibrating() const
{
const auto calibration_period = m_calibration_period_setting.GetValue();
return calibration_period && (Clock::now() - m_calibration_period_start) >=
std::chrono::duration<double>(calibration_period);
}
} // namespace ControllerEmu

View File

@ -4,11 +4,14 @@
#pragma once
#include <chrono>
#include <optional>
#include <string>
#include "Common/MathUtil.h"
#include "Common/Matrix.h"
#include "InputCommon/ControllerEmu/ControlGroup/ControlGroup.h"
#include "InputCommon/ControllerEmu/Setting/NumericSetting.h"
namespace ControllerEmu
{
@ -19,6 +22,25 @@ public:
IMUGyroscope(std::string name, std::string ui_name);
StateData GetRawState() const;
std::optional<StateData> GetState() const;
// Value is in rad/s.
ControlState GetDeadzone() const;
bool IsCalibrating() const;
private:
using Clock = std::chrono::steady_clock;
void RestartCalibration() const;
void UpdateCalibration(const StateData&) const;
SettingValue<double> m_deadzone_setting;
SettingValue<double> m_calibration_period_setting;
mutable StateData m_calibration = {};
mutable MathUtil::RunningMean<StateData> m_running_calibration;
mutable Clock::time_point m_calibration_period_start = Clock::now();
};
} // namespace ControllerEmu

View File

@ -11,6 +11,7 @@
#include <type_traits>
#include <vector>
#include "Common/BitUtils.h"
#include "Common/Common.h"
#include "Common/IniFile.h"
#include "InputCommon/ControlReference/ExpressionParser.h"
@ -27,6 +28,106 @@ namespace ControllerEmu
{
class ControlGroup;
// Represents calibration data found on Wii Remotes + extensions with a zero and a max value.
// (e.g. accelerometer data)
// Bits of precision specified to handle common situation of differing precision in the actual data.
template <typename T, size_t Bits>
struct TwoPointCalibration
{
TwoPointCalibration() = default;
TwoPointCalibration(const T& zero_, const T& max_) : zero{zero_}, max{max_} {}
static constexpr size_t BITS_OF_PRECISION = Bits;
T zero;
T max;
};
// Represents calibration data with a min, zero, and max value. (e.g. joystick data)
template <typename T, size_t Bits>
struct ThreePointCalibration
{
ThreePointCalibration() = default;
ThreePointCalibration(const T& min_, const T& zero_, const T& max_)
: min{min_}, zero{zero_}, max{max_}
{
}
static constexpr size_t BITS_OF_PRECISION = Bits;
T min;
T zero;
T max;
};
// Represents a raw/uncalibrated N-dimensional value of input data. (e.g. Joystick X and Y)
// A normalized value can be calculated with a provided {Two,Three}PointCalibration.
// Values are adjusted with mismatched bits of precision.
// Underlying type may be an unsigned type or a a Common::TVecN<> of an unsigned type.
template <typename T, size_t Bits>
struct RawValue
{
RawValue() = default;
explicit RawValue(const T& value_) : value{value_} {}
static constexpr size_t BITS_OF_PRECISION = Bits;
T value;
template <typename OtherT, size_t OtherBits>
auto GetNormalizedValue(const TwoPointCalibration<OtherT, OtherBits>& calibration) const
{
const auto value_expansion =
std::max(0, int(calibration.BITS_OF_PRECISION) - int(BITS_OF_PRECISION));
const auto calibration_expansion =
std::max(0, int(BITS_OF_PRECISION) - int(calibration.BITS_OF_PRECISION));
const auto calibration_zero = ExpandValue(calibration.zero, calibration_expansion) * 1.f;
const auto calibration_max = ExpandValue(calibration.max, calibration_expansion) * 1.f;
// Multiplication by 1.f to floatify either a scalar or a Vec.
return (ExpandValue(value, value_expansion) * 1.f - calibration_zero) /
(calibration_max - calibration_zero);
}
template <typename OtherT, size_t OtherBits>
auto GetNormalizedValue(const ThreePointCalibration<OtherT, OtherBits>& calibration) const
{
const auto value_expansion =
std::max(0, int(calibration.BITS_OF_PRECISION) - int(BITS_OF_PRECISION));
const auto calibration_expansion =
std::max(0, int(BITS_OF_PRECISION) - int(calibration.BITS_OF_PRECISION));
const auto calibration_min = ExpandValue(calibration.min, calibration_expansion) * 1.f;
const auto calibration_zero = ExpandValue(calibration.zero, calibration_expansion) * 1.f;
const auto calibration_max = ExpandValue(calibration.max, calibration_expansion) * 1.f;
const auto use_max = calibration.zero < value;
// Multiplication by 1.f to floatify either a scalar or a Vec.
return (ExpandValue(value, value_expansion) * 1.f - calibration_zero) /
(use_max * 1.f * (calibration_max - calibration_zero) +
!use_max * 1.f * (calibration_zero - calibration_min));
}
template <typename OtherT>
static OtherT ExpandValue(OtherT value, size_t bits)
{
if constexpr (std::is_arithmetic_v<OtherT>)
{
return Common::ExpandValue(value, bits);
}
else
{
for (size_t i = 0; i != std::size(value.data); ++i)
value.data[i] = Common::ExpandValue(value.data[i], bits);
return value;
}
}
};
class EmulatedController
{
public:

View File

@ -7,6 +7,7 @@
#include <algorithm>
#include "Common/Logging/Log.h"
#include "Core/HW/WiimoteReal/WiimoteReal.h"
#ifdef CIFACE_USE_WIN32
#include "InputCommon/ControllerInterface/Win32/Win32.h"
@ -93,7 +94,7 @@ void ControllerInterface::RefreshDevices()
return;
{
std::lock_guard<std::mutex> lk(m_devices_mutex);
std::lock_guard lk(m_devices_mutex);
m_devices.clear();
}
@ -132,6 +133,8 @@ void ControllerInterface::RefreshDevices()
ciface::DualShockUDPClient::PopulateDevices();
#endif
WiimoteReal::ProcessWiimotePool();
m_is_populating_devices = false;
InvokeDevicesChangedCallbacks();
}
@ -146,7 +149,7 @@ void ControllerInterface::Shutdown()
m_is_init = false;
{
std::lock_guard<std::mutex> lk(m_devices_mutex);
std::lock_guard lk(m_devices_mutex);
for (const auto& d : m_devices)
{
@ -193,7 +196,7 @@ void ControllerInterface::AddDevice(std::shared_ptr<ciface::Core::Device> device
return;
{
std::lock_guard<std::mutex> lk(m_devices_mutex);
std::lock_guard lk(m_devices_mutex);
const auto is_id_in_use = [&device, this](int id) {
return std::any_of(m_devices.begin(), m_devices.end(), [&device, &id](const auto& d) {
@ -229,7 +232,7 @@ void ControllerInterface::AddDevice(std::shared_ptr<ciface::Core::Device> device
void ControllerInterface::RemoveDevice(std::function<bool(const ciface::Core::Device*)> callback)
{
{
std::lock_guard<std::mutex> lk(m_devices_mutex);
std::lock_guard lk(m_devices_mutex);
auto it = std::remove_if(m_devices.begin(), m_devices.end(), [&callback](const auto& dev) {
if (callback(dev.get()))
{
@ -251,7 +254,7 @@ void ControllerInterface::UpdateInput()
// Don't block the UI or CPU thread (to avoid a short but noticeable frame drop)
if (m_devices_mutex.try_lock())
{
std::lock_guard<std::mutex> lk(m_devices_mutex, std::adopt_lock);
std::lock_guard lk(m_devices_mutex, std::adopt_lock);
for (const auto& d : m_devices)
d->UpdateInput();
}

View File

@ -180,7 +180,7 @@ bool DeviceQualifier::operator!=(const DeviceQualifier& devq) const
std::shared_ptr<Device> DeviceContainer::FindDevice(const DeviceQualifier& devq) const
{
std::lock_guard<std::mutex> lk(m_devices_mutex);
std::lock_guard lk(m_devices_mutex);
for (const auto& d : m_devices)
{
if (devq == d.get())
@ -192,7 +192,7 @@ std::shared_ptr<Device> DeviceContainer::FindDevice(const DeviceQualifier& devq)
std::vector<std::string> DeviceContainer::GetAllDeviceStrings() const
{
std::lock_guard<std::mutex> lk(m_devices_mutex);
std::lock_guard lk(m_devices_mutex);
std::vector<std::string> device_strings;
DeviceQualifier device_qualifier;
@ -208,7 +208,7 @@ std::vector<std::string> DeviceContainer::GetAllDeviceStrings() const
std::string DeviceContainer::GetDefaultDeviceString() const
{
std::lock_guard<std::mutex> lk(m_devices_mutex);
std::lock_guard lk(m_devices_mutex);
if (m_devices.empty())
return "";
@ -226,7 +226,7 @@ Device::Input* DeviceContainer::FindInput(std::string_view name, const Device* d
return inp;
}
std::lock_guard<std::mutex> lk(m_devices_mutex);
std::lock_guard lk(m_devices_mutex);
for (const auto& d : m_devices)
{
Device::Input* const i = d->FindInput(name);

View File

@ -198,7 +198,7 @@ public:
DetectInput(u32 wait_ms, const std::vector<std::string>& device_strings) const;
protected:
mutable std::mutex m_devices_mutex;
mutable std::recursive_mutex m_devices_mutex;
std::vector<std::shared_ptr<Device>> m_devices;
};
} // namespace Core

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,270 @@
// Copyright 2020 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#pragma once
#include <array>
#include <chrono>
#include <memory>
#include <vector>
#include "Core/HW/WiimoteCommon/DataReport.h"
#include "Core/HW/WiimoteCommon/WiimoteReport.h"
#include "Core/HW/WiimoteEmu/Camera.h"
#include "Core/HW/WiimoteEmu/Extension/Classic.h"
#include "Core/HW/WiimoteEmu/Extension/Nunchuk.h"
#include "Core/HW/WiimoteEmu/MotionPlus.h"
#include "Core/HW/WiimoteReal/WiimoteReal.h"
#include "InputCommon/ControllerInterface/Device.h"
namespace ciface::Wiimote
{
using namespace WiimoteCommon;
void AddDevice(std::unique_ptr<WiimoteReal::Wiimote>);
void ReleaseDevices(std::optional<u32> count = std::nullopt);
class Device final : public Core::Device
{
public:
Device(std::unique_ptr<WiimoteReal::Wiimote> wiimote);
~Device();
std::string GetName() const override;
std::string GetSource() const override;
void UpdateInput() override;
private:
using Clock = std::chrono::steady_clock;
enum class ExtensionID
{
Nunchuk,
Classic,
Unsupported,
};
class MotionPlusState
{
public:
void SetCalibrationData(const WiimoteEmu::MotionPlus::CalibrationData&);
void ProcessData(const WiimoteEmu::MotionPlus::DataFormat&);
using PassthroughMode = WiimoteEmu::MotionPlus::PassthroughMode;
// State is unknown by default.
std::optional<PassthroughMode> current_mode;
// The last known state of the passthrough port flag.
// Used to detect passthrough extension port events.
std::optional<bool> passthrough_port;
Common::Vec3 gyro_data = {};
std::optional<WiimoteEmu::MotionPlus::CalibrationBlocks> calibration;
};
struct NunchukState
{
using CalibrationData = WiimoteEmu::Nunchuk::CalibrationData;
void SetCalibrationData(const CalibrationData&);
void ProcessData(const WiimoteEmu::Nunchuk::DataFormat&);
Common::Vec2 stick = {};
Common::Vec3 accel = {};
u8 buttons = 0;
struct Calibration
{
CalibrationData::AccelCalibration accel;
CalibrationData::StickCalibration stick;
};
std::optional<Calibration> calibration;
};
struct ClassicState
{
using CalibrationData = WiimoteEmu::Classic::CalibrationData;
void SetCalibrationData(const CalibrationData&);
void ProcessData(const WiimoteEmu::Classic::DataFormat&);
std::array<Common::Vec2, 2> sticks = {};
std::array<float, 2> triggers = {};
u16 buttons = 0;
struct Calibration
{
CalibrationData::StickCalibration left_stick;
CalibrationData::StickCalibration right_stick;
CalibrationData::TriggerCalibration left_trigger;
CalibrationData::TriggerCalibration right_trigger;
};
std::optional<Calibration> calibration;
};
struct IRState
{
static u32 GetDesiredIRSensitivity();
void ProcessData(const std::array<WiimoteEmu::IRBasic, 2>&);
bool IsFullyConfigured() const;
u32 current_sensitivity = u32(-1);
bool enabled = false;
bool mode_set = false;
// Average of visible IR "objects".
Common::Vec2 center_position = {};
bool is_hidden = true;
};
class ReportHandler
{
public:
enum class HandlerResult
{
Handled,
NotHandled,
};
ReportHandler(Clock::time_point expired_time);
template <typename R, typename T>
void AddHandler(std::function<R(const T&)>);
HandlerResult TryToHandleReport(const WiimoteReal::Report& report);
bool IsExpired() const;
private:
const Clock::time_point m_expired_time;
std::vector<std::function<HandlerResult(const WiimoteReal::Report& report)>> m_callbacks;
};
using AckReportHandler = std::function<ReportHandler::HandlerResult(const InputReportAck& reply)>;
static AckReportHandler MakeAckHandler(OutputReportID report_id,
std::function<void(WiimoteCommon::ErrorCode)> callback);
// TODO: Make parameter const. (need to modify DataReportManipulator)
void ProcessInputReport(WiimoteReal::Report& report);
void ProcessMotionPlusExtensionData(const u8* data, u32 size);
void ProcessNormalExtensionData(const u8* data, u32 size);
void ProcessExtensionEvent(bool connected);
void ProcessExtensionID(u8 id_0, u8 id_4, u8 id_5);
void ProcessStatusReport(const InputReportStatus&);
void RunTasks();
bool IsPerformingTask() const;
template <typename T>
void QueueReport(T&& report, std::function<void(ErrorCode)> ack_callback = {});
template <typename... T>
void AddReportHandler(T&&... callbacks);
using ReadResponse = std::optional<std::vector<u8>>;
void ReadData(AddressSpace space, u8 slave, u16 address, u16 size,
std::function<void(ReadResponse)> callback);
void AddReadDataReplyHandler(AddressSpace space, u8 slave, u16 address, u16 size,
std::vector<u8> starting_data,
std::function<void(ReadResponse)> callback);
template <typename T = std::initializer_list<u8>, typename C>
void WriteData(AddressSpace space, u8 slave, u16 address, T&& data, C&& callback);
void ReadActiveExtensionID();
void SetIRSensitivity(u32 level);
void ConfigureSpeaker();
void ConfigureIRCamera();
u8 GetDesiredLEDValue() const;
void TriggerMotionPlusModeChange();
void TriggerMotionPlusCalibration();
bool IsMotionPlusStateKnown() const;
bool IsMotionPlusActive() const;
bool IsMotionPlusInDesiredMode() const;
bool IsWaitingForMotionPlus() const;
void WaitForMotionPlus();
void HandleMotionPlusNonResponse();
void UpdateRumble();
void UpdateOrientation();
void UpdateExtensionNumberInput();
std::unique_ptr<WiimoteReal::Wiimote> m_wiimote;
// Buttons.
DataReportManipulator::CoreData m_core_data = {};
// Accelerometer.
Common::Vec3 m_accel_data = {};
std::optional<AccelCalibrationData::Calibration> m_accel_calibration;
// Pitch, Roll, Yaw inputs.
Common::Vec3 m_rotation_inputs = {};
MotionPlusState m_mplus_state = {};
NunchukState m_nunchuk_state = {};
ClassicState m_classic_state = {};
IRState m_ir_state = {};
// Used to poll for M+ periodically and wait for it to reset.
Clock::time_point m_mplus_wait_time = Clock::now();
// The desired mode is set based on the attached normal extension.
std::optional<MotionPlusState::PassthroughMode> m_mplus_desired_mode;
// Status report is requested every so often to update the battery level.
Clock::time_point m_status_outdated_time = Clock::now();
u8 m_battery = 0;
u8 m_leds = 0;
bool m_speaker_configured = false;
// The last known state of the extension port status flag.
// Used to detect extension port events.
std::optional<bool> m_extension_port;
// Note this refers to the passthrough extension when M+ is active.
std::optional<ExtensionID> m_extension_id;
// Rumble state must be saved to set the proper flag in every output report.
bool m_rumble = false;
// For pulse of rumble motor to simulate multiple levels.
ControlState m_rumble_level = 0;
Clock::time_point m_last_rumble_change = Clock::now();
// Assume mode is disabled so one gets set.
InputReportID m_reporting_mode = InputReportID::ReportDisabled;
// Used only to provide a value for a specialty "input". (for attached extension passthrough)
WiimoteEmu::ExtensionNumber m_extension_number_input = WiimoteEmu::ExtensionNumber::NONE;
bool m_mplus_attached_input = false;
// Holds callbacks for output report replies.
std::list<ReportHandler> m_report_handlers;
// World rotation. (used to rotate IR data and provide pitch, roll, yaw inputs)
Common::Matrix33 m_orientation = Common::Matrix33::Identity();
Clock::time_point m_last_report_time = Clock::now();
};
} // namespace ciface::Wiimote

View File

@ -75,6 +75,7 @@
<ClCompile Include="ControlReference\ExpressionParser.cpp" />
<ClCompile Include="ControllerInterface\ForceFeedback\ForceFeedbackDevice.cpp" />
<ClCompile Include="ControllerInterface\Win32\Win32.cpp" />
<ClCompile Include="ControllerInterface\Wiimote\Wiimote.cpp" />
<ClCompile Include="ControllerInterface\XInput\XInput.cpp" />
<ClCompile Include="ControlReference\FunctionExpression.cpp" />
<ClCompile Include="GCAdapter.cpp">
@ -122,6 +123,7 @@
<ClInclude Include="ControlReference\ExpressionParser.h" />
<ClInclude Include="ControllerInterface\ForceFeedback\ForceFeedbackDevice.h" />
<ClInclude Include="ControllerInterface\Win32\Win32.h" />
<ClInclude Include="ControllerInterface\Wiimote\Wiimote.h" />
<ClInclude Include="ControllerInterface\XInput\XInput.h" />
<ClInclude Include="GCAdapter.h" />
<ClInclude Include="GCPadStatus.h" />
@ -139,4 +141,4 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>
</Project>

View File

@ -110,6 +110,9 @@
<ClCompile Include="ControllerInterface\Win32\Win32.cpp">
<Filter>ControllerInterface\Win32</Filter>
</ClCompile>
<ClCompile Include="ControllerInterface\Wiimote\Wiimote.cpp">
<Filter>ControllerInterface\Wiimote</Filter>
</ClCompile>
<ClCompile Include="ControlReference\ExpressionParser.cpp">
<Filter>ControllerInterface</Filter>
</ClCompile>
@ -218,6 +221,9 @@
<ClInclude Include="ControllerInterface\Win32\Win32.h">
<Filter>ControllerInterface\Win32</Filter>
</ClInclude>
<ClInclude Include="ControllerInterface\Wiimote\Wiimote.h">
<Filter>ControllerInterface\Wiimote</Filter>
</ClInclude>
<ClInclude Include="ControlReference\ExpressionParser.h">
<Filter>ControllerInterface</Filter>
</ClInclude>
@ -248,4 +254,4 @@
<ItemGroup>
<Text Include="CMakeLists.txt" />
</ItemGroup>
</Project>
</Project>