Merge pull request #8575 from jordan-woyak/ciface-wiimotes
InputCommon: Add support for Wii Remotes in ControllerInterface
This commit is contained in:
commit
2b6a1ee4d8
|
@ -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
|
||||
|
|
|
@ -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>&);
|
||||
|
|
|
@ -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>;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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++)
|
||||
{
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
@ -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
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue