mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-07-24 14:49:42 -06:00
Merge pull request #8575 from jordan-woyak/ciface-wiimotes
InputCommon: Add support for Wii Remotes in ControllerInterface
This commit is contained in:
@ -300,6 +300,12 @@ void SetBit(T& value, size_t bit_number, bool bit_value)
|
|||||||
value &= ~(T{1} << bit_number);
|
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>
|
template <typename T>
|
||||||
class FlagBit
|
class FlagBit
|
||||||
{
|
{
|
||||||
@ -340,4 +346,15 @@ public:
|
|||||||
std::underlying_type_t<T> m_hex = 0;
|
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
|
} // namespace Common
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "Common/CommonTypes.h"
|
#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
|
} // namespace MathUtil
|
||||||
|
|
||||||
float MathFloatVectorSum(const std::vector<float>&);
|
float MathFloatVectorSum(const std::vector<float>&);
|
||||||
|
@ -20,6 +20,11 @@ union TVec3
|
|||||||
TVec3() = default;
|
TVec3() = default;
|
||||||
TVec3(T _x, T _y, T _z) : data{_x, _y, _z} {}
|
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
|
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)};
|
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);
|
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>
|
template <typename T>
|
||||||
auto operator+(const TVec3<T>& lhs, const TVec3<T>& rhs) -> TVec3<decltype(lhs.x + rhs.x)>
|
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() = default;
|
||||||
TVec2(T _x, T _y) : data{_x, _y} {}
|
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 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 Dot(const TVec2& rhs) const { return (x * rhs.x) + (y * rhs.y); }
|
||||||
T LengthSquared() const { return Dot(*this); }
|
T LengthSquared() const { return Dot(*this); }
|
||||||
@ -217,6 +232,20 @@ union TVec2
|
|||||||
return *this;
|
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)
|
TVec2& operator*=(T scalar)
|
||||||
{
|
{
|
||||||
x *= 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>
|
template <typename T>
|
||||||
TVec2<T> operator+(TVec2<T> lhs, const TVec2<T>& rhs)
|
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>
|
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>
|
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>;
|
using Vec2 = TVec2<float>;
|
||||||
|
@ -234,6 +234,7 @@ void SConfig::SaveCoreSettings(IniFile& ini)
|
|||||||
core->Set("WiiKeyboard", m_WiiKeyboard);
|
core->Set("WiiKeyboard", m_WiiKeyboard);
|
||||||
core->Set("WiimoteContinuousScanning", m_WiimoteContinuousScanning);
|
core->Set("WiimoteContinuousScanning", m_WiimoteContinuousScanning);
|
||||||
core->Set("WiimoteEnableSpeaker", m_WiimoteEnableSpeaker);
|
core->Set("WiimoteEnableSpeaker", m_WiimoteEnableSpeaker);
|
||||||
|
core->Set("WiimoteControllerInterface", connect_wiimotes_for_ciface);
|
||||||
core->Set("RunCompareServer", bRunCompareServer);
|
core->Set("RunCompareServer", bRunCompareServer);
|
||||||
core->Set("RunCompareClient", bRunCompareClient);
|
core->Set("RunCompareClient", bRunCompareClient);
|
||||||
core->Set("MMU", bMMU);
|
core->Set("MMU", bMMU);
|
||||||
@ -511,6 +512,7 @@ void SConfig::LoadCoreSettings(IniFile& ini)
|
|||||||
core->Get("WiiKeyboard", &m_WiiKeyboard, false);
|
core->Get("WiiKeyboard", &m_WiiKeyboard, false);
|
||||||
core->Get("WiimoteContinuousScanning", &m_WiimoteContinuousScanning, false);
|
core->Get("WiimoteContinuousScanning", &m_WiimoteContinuousScanning, false);
|
||||||
core->Get("WiimoteEnableSpeaker", &m_WiimoteEnableSpeaker, false);
|
core->Get("WiimoteEnableSpeaker", &m_WiimoteEnableSpeaker, false);
|
||||||
|
core->Get("WiimoteControllerInterface", &connect_wiimotes_for_ciface, false);
|
||||||
core->Get("RunCompareServer", &bRunCompareServer, false);
|
core->Get("RunCompareServer", &bRunCompareServer, false);
|
||||||
core->Get("RunCompareClient", &bRunCompareClient, false);
|
core->Get("RunCompareClient", &bRunCompareClient, false);
|
||||||
core->Get("MMU", &bMMU, bMMU);
|
core->Get("MMU", &bMMU, bMMU);
|
||||||
|
@ -72,6 +72,7 @@ struct SConfig
|
|||||||
bool m_WiiKeyboard;
|
bool m_WiiKeyboard;
|
||||||
bool m_WiimoteContinuousScanning;
|
bool m_WiimoteContinuousScanning;
|
||||||
bool m_WiimoteEnableSpeaker;
|
bool m_WiimoteEnableSpeaker;
|
||||||
|
bool connect_wiimotes_for_ciface;
|
||||||
|
|
||||||
// ISO folder
|
// ISO folder
|
||||||
std::vector<std::string> m_ISOFolder;
|
std::vector<std::string> m_ISOFolder;
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
|
||||||
#include "Common/BitUtils.h"
|
#include "Common/BitUtils.h"
|
||||||
|
#include "Common/MathUtil.h"
|
||||||
#include "Core/HW/WiimoteCommon/DataReport.h"
|
#include "Core/HW/WiimoteCommon/DataReport.h"
|
||||||
|
|
||||||
namespace WiimoteCommon
|
namespace WiimoteCommon
|
||||||
@ -75,40 +76,35 @@ struct IncludeAccel : virtual DataReportManipulator
|
|||||||
void GetAccelData(AccelData* result) const override
|
void GetAccelData(AccelData* result) const override
|
||||||
{
|
{
|
||||||
const AccelMSB accel = Common::BitCastPtr<AccelMSB>(data_ptr + 2);
|
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);
|
const CoreData core = Common::BitCastPtr<CoreData>(data_ptr);
|
||||||
result->x |= core.acc_bits & 0b11;
|
|
||||||
result->y |= (core.acc_bits2 & 0b1) << 1;
|
// X has 10 bits of precision.
|
||||||
result->z |= core.acc_bits2 & 0b10;
|
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
|
void SetAccelData(const AccelData& new_accel) override
|
||||||
{
|
{
|
||||||
AccelMSB accel = {};
|
Common::BitCastPtr<AccelMSB>(data_ptr + 2) = AccelMSB(new_accel.value / 4);
|
||||||
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;
|
|
||||||
|
|
||||||
// LSBs
|
// LSBs
|
||||||
CoreData core = Common::BitCastPtr<CoreData>(data_ptr);
|
CoreData core = Common::BitCastPtr<CoreData>(data_ptr);
|
||||||
core.acc_bits = (new_accel.x >> 0) & 0b11;
|
core.acc_bits = (new_accel.value.x >> 0) & 0b11;
|
||||||
core.acc_bits2 = (new_accel.y >> 1) & 0x1;
|
core.acc_bits2 = (new_accel.value.y >> 1) & 0x1;
|
||||||
core.acc_bits2 |= (new_accel.z & 0xb10);
|
core.acc_bits2 |= (new_accel.value.z & 0xb10);
|
||||||
Common::BitCastPtr<CoreData>(data_ptr) = core;
|
Common::BitCastPtr<CoreData>(data_ptr) = core;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HasAccel() const override { return true; }
|
bool HasAccel() const override { return true; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct AccelMSB
|
using AccelMSB = Common::TVec3<u8>;
|
||||||
{
|
|
||||||
u8 x, y, z;
|
|
||||||
};
|
|
||||||
static_assert(sizeof(AccelMSB) == 3, "Wrong size");
|
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
|
struct ReportInterleave1 : IncludeCore, IncludeIR<3, 18, 0>, NoExt
|
||||||
{
|
{
|
||||||
// FYI: Only 8-bits of precision in this report, and no Y axis.
|
// 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
|
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.
|
// Y axis is not contained in this report. (provided by "Interleave2")
|
||||||
accel->z &= 0b111111;
|
|
||||||
|
|
||||||
|
// 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);
|
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
|
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);
|
CoreData core = Common::BitCastPtr<CoreData>(data_ptr);
|
||||||
core.acc_bits = (accel.z >> 6) & 0b11;
|
core.acc_bits = (accel.value.z >> 6) & 0b11;
|
||||||
core.acc_bits2 = (accel.z >> 8) & 0b11;
|
core.acc_bits2 = (accel.value.z >> 8) & 0b11;
|
||||||
Common::BitCastPtr<CoreData>(data_ptr) = core;
|
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
|
struct ReportInterleave2 : IncludeCore, IncludeIR<3, 18, 18>, NoExt
|
||||||
{
|
{
|
||||||
// FYI: Only 8-bits of precision in this report, and no X axis.
|
// 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
|
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.
|
// Y axis only has 8 bits of precision. (converted to 10)
|
||||||
accel->z &= ~0b111111;
|
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);
|
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
|
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);
|
CoreData core = Common::BitCastPtr<CoreData>(data_ptr);
|
||||||
core.acc_bits = (accel.z >> 2) & 0b11;
|
core.acc_bits = (accel.value.z >> 2) & 0b11;
|
||||||
core.acc_bits2 = (accel.z >> 4) & 0b11;
|
core.acc_bits2 = (accel.value.z >> 4) & 0b11;
|
||||||
Common::BitCastPtr<CoreData>(data_ptr) = core;
|
Common::BitCastPtr<CoreData>(data_ptr) = core;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,9 +8,11 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
#include "Common/CommonTypes.h"
|
#include "Common/CommonTypes.h"
|
||||||
|
#include "Common/Matrix.h"
|
||||||
#include "Core/HW/WiimoteCommon/WiimoteConstants.h"
|
#include "Core/HW/WiimoteCommon/WiimoteConstants.h"
|
||||||
#include "Core/HW/WiimoteCommon/WiimoteHid.h"
|
#include "Core/HW/WiimoteCommon/WiimoteHid.h"
|
||||||
#include "Core/HW/WiimoteCommon/WiimoteReport.h"
|
#include "Core/HW/WiimoteCommon/WiimoteReport.h"
|
||||||
|
#include "InputCommon/ControllerEmu/ControllerEmu.h"
|
||||||
|
|
||||||
namespace WiimoteCommon
|
namespace WiimoteCommon
|
||||||
{
|
{
|
||||||
@ -21,12 +23,6 @@ class DataReportManipulator
|
|||||||
public:
|
public:
|
||||||
virtual ~DataReportManipulator() = default;
|
virtual ~DataReportManipulator() = default;
|
||||||
|
|
||||||
// Accel data handled as if there were always 10 bits of precision.
|
|
||||||
struct AccelData
|
|
||||||
{
|
|
||||||
u16 x, y, z;
|
|
||||||
};
|
|
||||||
|
|
||||||
using CoreData = ButtonData;
|
using CoreData = ButtonData;
|
||||||
|
|
||||||
virtual bool HasCore() const = 0;
|
virtual bool HasCore() const = 0;
|
||||||
@ -66,7 +62,6 @@ public:
|
|||||||
explicit DataReportBuilder(InputReportID rpt_id);
|
explicit DataReportBuilder(InputReportID rpt_id);
|
||||||
|
|
||||||
using CoreData = ButtonData;
|
using CoreData = ButtonData;
|
||||||
using AccelData = DataReportManipulator::AccelData;
|
|
||||||
|
|
||||||
void SetMode(InputReportID rpt_id);
|
void SetMode(InputReportID rpt_id);
|
||||||
InputReportID GetMode() const;
|
InputReportID GetMode() const;
|
||||||
@ -99,11 +94,10 @@ public:
|
|||||||
|
|
||||||
u32 GetDataSize() const;
|
u32 GetDataSize() const;
|
||||||
|
|
||||||
private:
|
|
||||||
static constexpr int HEADER_SIZE = 2;
|
static constexpr int HEADER_SIZE = 2;
|
||||||
|
|
||||||
static constexpr int MAX_DATA_SIZE = MAX_PAYLOAD - 2;
|
static constexpr int MAX_DATA_SIZE = MAX_PAYLOAD - 2;
|
||||||
|
|
||||||
|
private:
|
||||||
TypedHIDInputData<std::array<u8, MAX_DATA_SIZE>> m_data;
|
TypedHIDInputData<std::array<u8, MAX_DATA_SIZE>> m_data;
|
||||||
|
|
||||||
std::unique_ptr<DataReportManipulator> m_manip;
|
std::unique_ptr<DataReportManipulator> m_manip;
|
||||||
|
@ -10,6 +10,10 @@ namespace WiimoteCommon
|
|||||||
{
|
{
|
||||||
constexpr u8 MAX_PAYLOAD = 23;
|
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
|
enum class InputReportID : u8
|
||||||
{
|
{
|
||||||
Status = 0x20,
|
Status = 0x20,
|
||||||
|
@ -7,7 +7,9 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "Common/CommonTypes.h"
|
#include "Common/CommonTypes.h"
|
||||||
|
#include "Common/Matrix.h"
|
||||||
#include "Core/HW/WiimoteCommon/WiimoteConstants.h"
|
#include "Core/HW/WiimoteCommon/WiimoteConstants.h"
|
||||||
|
#include "InputCommon/ControllerEmu/ControllerEmu.h"
|
||||||
|
|
||||||
#ifdef _MSC_VER
|
#ifdef _MSC_VER
|
||||||
#pragma warning(push)
|
#pragma warning(push)
|
||||||
@ -41,6 +43,8 @@ static_assert(sizeof(OutputReportGeneric) == 2, "Wrong size");
|
|||||||
|
|
||||||
struct OutputReportRumble
|
struct OutputReportRumble
|
||||||
{
|
{
|
||||||
|
static constexpr OutputReportID REPORT_ID = OutputReportID::Rumble;
|
||||||
|
|
||||||
u8 rumble : 1;
|
u8 rumble : 1;
|
||||||
};
|
};
|
||||||
static_assert(sizeof(OutputReportRumble) == 1, "Wrong size");
|
static_assert(sizeof(OutputReportRumble) == 1, "Wrong size");
|
||||||
@ -55,8 +59,34 @@ struct OutputReportEnableFeature
|
|||||||
};
|
};
|
||||||
static_assert(sizeof(OutputReportEnableFeature) == 1, "Wrong size");
|
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
|
struct OutputReportLeds
|
||||||
{
|
{
|
||||||
|
static constexpr OutputReportID REPORT_ID = OutputReportID::LED;
|
||||||
|
|
||||||
u8 rumble : 1;
|
u8 rumble : 1;
|
||||||
u8 ack : 1;
|
u8 ack : 1;
|
||||||
u8 : 2;
|
u8 : 2;
|
||||||
@ -66,6 +96,8 @@ static_assert(sizeof(OutputReportLeds) == 1, "Wrong size");
|
|||||||
|
|
||||||
struct OutputReportMode
|
struct OutputReportMode
|
||||||
{
|
{
|
||||||
|
static constexpr OutputReportID REPORT_ID = OutputReportID::ReportMode;
|
||||||
|
|
||||||
u8 rumble : 1;
|
u8 rumble : 1;
|
||||||
u8 ack : 1;
|
u8 ack : 1;
|
||||||
u8 continuous : 1;
|
u8 continuous : 1;
|
||||||
@ -76,6 +108,8 @@ static_assert(sizeof(OutputReportMode) == 2, "Wrong size");
|
|||||||
|
|
||||||
struct OutputReportRequestStatus
|
struct OutputReportRequestStatus
|
||||||
{
|
{
|
||||||
|
static constexpr OutputReportID REPORT_ID = OutputReportID::RequestStatus;
|
||||||
|
|
||||||
u8 rumble : 1;
|
u8 rumble : 1;
|
||||||
u8 : 7;
|
u8 : 7;
|
||||||
};
|
};
|
||||||
@ -83,6 +117,8 @@ static_assert(sizeof(OutputReportRequestStatus) == 1, "Wrong size");
|
|||||||
|
|
||||||
struct OutputReportWriteData
|
struct OutputReportWriteData
|
||||||
{
|
{
|
||||||
|
static constexpr OutputReportID REPORT_ID = OutputReportID::WriteData;
|
||||||
|
|
||||||
u8 rumble : 1;
|
u8 rumble : 1;
|
||||||
u8 : 1;
|
u8 : 1;
|
||||||
u8 space : 2;
|
u8 space : 2;
|
||||||
@ -100,6 +136,8 @@ static_assert(sizeof(OutputReportWriteData) == 21, "Wrong size");
|
|||||||
|
|
||||||
struct OutputReportReadData
|
struct OutputReportReadData
|
||||||
{
|
{
|
||||||
|
static constexpr OutputReportID REPORT_ID = OutputReportID::ReadData;
|
||||||
|
|
||||||
u8 rumble : 1;
|
u8 rumble : 1;
|
||||||
u8 : 1;
|
u8 : 1;
|
||||||
u8 space : 2;
|
u8 space : 2;
|
||||||
@ -116,6 +154,8 @@ static_assert(sizeof(OutputReportReadData) == 6, "Wrong size");
|
|||||||
|
|
||||||
struct OutputReportSpeakerData
|
struct OutputReportSpeakerData
|
||||||
{
|
{
|
||||||
|
static constexpr OutputReportID REPORT_ID = OutputReportID::SpeakerData;
|
||||||
|
|
||||||
u8 rumble : 1;
|
u8 rumble : 1;
|
||||||
u8 : 2;
|
u8 : 2;
|
||||||
u8 length : 5;
|
u8 length : 5;
|
||||||
@ -157,6 +197,8 @@ static_assert(sizeof(ButtonData) == 2, "Wrong size");
|
|||||||
|
|
||||||
struct InputReportStatus
|
struct InputReportStatus
|
||||||
{
|
{
|
||||||
|
static constexpr InputReportID REPORT_ID = InputReportID::Status;
|
||||||
|
|
||||||
ButtonData buttons;
|
ButtonData buttons;
|
||||||
u8 battery_low : 1;
|
u8 battery_low : 1;
|
||||||
u8 extension : 1;
|
u8 extension : 1;
|
||||||
@ -170,6 +212,8 @@ static_assert(sizeof(InputReportStatus) == 6, "Wrong size");
|
|||||||
|
|
||||||
struct InputReportAck
|
struct InputReportAck
|
||||||
{
|
{
|
||||||
|
static constexpr InputReportID REPORT_ID = InputReportID::Ack;
|
||||||
|
|
||||||
ButtonData buttons;
|
ButtonData buttons;
|
||||||
OutputReportID rpt_id;
|
OutputReportID rpt_id;
|
||||||
ErrorCode error_code;
|
ErrorCode error_code;
|
||||||
@ -178,6 +222,8 @@ static_assert(sizeof(InputReportAck) == 4, "Wrong size");
|
|||||||
|
|
||||||
struct InputReportReadDataReply
|
struct InputReportReadDataReply
|
||||||
{
|
{
|
||||||
|
static constexpr InputReportID REPORT_ID = InputReportID::ReadDataReply;
|
||||||
|
|
||||||
ButtonData buttons;
|
ButtonData buttons;
|
||||||
u8 error : 4;
|
u8 error : 4;
|
||||||
u8 size_minus_one : 4;
|
u8 size_minus_one : 4;
|
||||||
@ -187,6 +233,64 @@ struct InputReportReadDataReply
|
|||||||
};
|
};
|
||||||
static_assert(sizeof(InputReportReadDataReply) == 21, "Wrong size");
|
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
|
} // namespace WiimoteCommon
|
||||||
|
|
||||||
#pragma pack(pop)
|
#pragma pack(pop)
|
||||||
|
@ -60,14 +60,6 @@ void CameraLogic::Update(const Common::Matrix44& transform)
|
|||||||
using Common::Vec3;
|
using Common::Vec3;
|
||||||
using Common::Vec4;
|
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_FOV_Y = float(CAMERA_FOV_Y_DEG * MathUtil::TAU / 360);
|
||||||
constexpr auto CAMERA_ASPECT_RATIO = float(CAMERA_FOV_X_DEG) / CAMERA_FOV_Y_DEG;
|
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)
|
if (point.z > 0)
|
||||||
{
|
{
|
||||||
// FYI: Casting down vs. rounding seems to produce more symmetrical output.
|
// 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 x = s32((1 - point.x / point.w) * CAMERA_RES_X / 2);
|
||||||
const auto y = s32((1 - point.y / point.w) * CAMERA_HEIGHT / 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);
|
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)};
|
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)
|
for (std::size_t i = 0; i != camera_points.size(); ++i)
|
||||||
{
|
{
|
||||||
const auto& p = camera_points[i];
|
const auto& p = camera_points[i];
|
||||||
if (p.x < CAMERA_WIDTH)
|
if (p.x < CAMERA_RES_X)
|
||||||
{
|
{
|
||||||
IRExtended irdata = {};
|
IRExtended irdata = {};
|
||||||
|
|
||||||
@ -186,7 +178,7 @@ void CameraLogic::Update(const Common::Matrix44& transform)
|
|||||||
for (std::size_t i = 0; i != camera_points.size(); ++i)
|
for (std::size_t i = 0; i != camera_points.size(); ++i)
|
||||||
{
|
{
|
||||||
const auto& p = camera_points[i];
|
const auto& p = camera_points[i];
|
||||||
if (p.x < CAMERA_WIDTH)
|
if (p.x < CAMERA_RES_X)
|
||||||
{
|
{
|
||||||
IRFull irdata = {};
|
IRFull irdata = {};
|
||||||
|
|
||||||
@ -203,8 +195,8 @@ void CameraLogic::Update(const Common::Matrix44& transform)
|
|||||||
|
|
||||||
irdata.xmin = std::max(p.x - p.size, 0);
|
irdata.xmin = std::max(p.x - p.size, 0);
|
||||||
irdata.ymin = std::max(p.y - p.size, 0);
|
irdata.ymin = std::max(p.y - p.size, 0);
|
||||||
irdata.xmax = std::min(p.x + p.size, CAMERA_WIDTH);
|
irdata.xmax = std::min(p.x + p.size, CAMERA_RES_X);
|
||||||
irdata.ymax = std::min(p.y + p.size, CAMERA_HEIGHT);
|
irdata.ymax = std::min(p.y + p.size, CAMERA_RES_Y);
|
||||||
|
|
||||||
// TODO: Is this maybe MSbs of the "intensity" value?
|
// TODO: Is this maybe MSbs of the "intensity" value?
|
||||||
irdata.zero = 0;
|
irdata.zero = 0;
|
||||||
|
@ -20,6 +20,8 @@ namespace WiimoteEmu
|
|||||||
// Four bytes for two objects. Filled with 0xFF if empty
|
// Four bytes for two objects. Filled with 0xFF if empty
|
||||||
struct IRBasic
|
struct IRBasic
|
||||||
{
|
{
|
||||||
|
using IRObject = Common::TVec2<u16>;
|
||||||
|
|
||||||
u8 x1;
|
u8 x1;
|
||||||
u8 y1;
|
u8 y1;
|
||||||
u8 x2hi : 2;
|
u8 x2hi : 2;
|
||||||
@ -28,6 +30,9 @@ struct IRBasic
|
|||||||
u8 y1hi : 2;
|
u8 y1hi : 2;
|
||||||
u8 x2;
|
u8 x2;
|
||||||
u8 y2;
|
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");
|
static_assert(sizeof(IRBasic) == 5, "Wrong size");
|
||||||
|
|
||||||
@ -62,6 +67,14 @@ static_assert(sizeof(IRFull) == 9, "Wrong size");
|
|||||||
class CameraLogic : public I2CSlave
|
class CameraLogic : public I2CSlave
|
||||||
{
|
{
|
||||||
public:
|
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
|
enum : u8
|
||||||
{
|
{
|
||||||
IR_MODE_BASIC = 1,
|
IR_MODE_BASIC = 1,
|
||||||
|
@ -54,11 +54,15 @@ double CalculateStopDistance(double velocity, double max_accel)
|
|||||||
return velocity * velocity / (2 * std::copysign(max_accel, velocity));
|
return velocity * velocity / (2 * std::copysign(max_accel, velocity));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note that 'gyroscope' is rotation of world around device.
|
} // namespace
|
||||||
Common::Matrix33 ComplementaryFilter(const Common::Vec3& accelerometer,
|
|
||||||
const Common::Matrix33& gyroscope, float accel_weight)
|
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 normalized_accel = accelerometer.Normalized();
|
||||||
|
|
||||||
const auto cos_angle = normalized_accel.Dot(gyro_vec);
|
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()}
|
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,
|
WiimoteCommon::AccelData ConvertAccelData(const Common::Vec3& accel, u16 zero_g, u16 one_g)
|
||||||
u16 one_g)
|
|
||||||
{
|
{
|
||||||
const auto scaled_accel = accel * (one_g - zero_g) / float(GRAVITY_ACCELERATION);
|
const auto scaled_accel = accel * (one_g - zero_g) / float(GRAVITY_ACCELERATION);
|
||||||
|
|
||||||
// 10-bit integers.
|
// 10-bit integers.
|
||||||
constexpr long MAX_VALUE = (1 << 10) - 1;
|
constexpr long MAX_VALUE = (1 << 10) - 1;
|
||||||
|
|
||||||
return {u16(std::clamp(std::lround(scaled_accel.x + zero_g), 0l, MAX_VALUE)),
|
return WiimoteCommon::AccelData(
|
||||||
u16(std::clamp(std::lround(scaled_accel.y + zero_g), 0l, MAX_VALUE)),
|
{u16(std::clamp(std::lround(scaled_accel.x + zero_g), 0l, MAX_VALUE)),
|
||||||
u16(std::clamp(std::lround(scaled_accel.z + 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)
|
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.
|
// Apply rotation from gyro data.
|
||||||
const auto gyro_rotation = Common::Matrix33::FromQuaternion(ang_vel->x * time_elapsed / -2,
|
const auto gyro_rotation = GetMatrixFromGyroscope(*ang_vel * -1 * time_elapsed);
|
||||||
ang_vel->y * time_elapsed / -2,
|
|
||||||
ang_vel->z * time_elapsed / -2, 1);
|
|
||||||
state->rotation = gyro_rotation * state->rotation;
|
state->rotation = gyro_rotation * state->rotation;
|
||||||
|
|
||||||
// If we have some non-zero accel data use it to adjust gyro drift.
|
// If we have some non-zero accel data use it to adjust gyro drift.
|
||||||
constexpr auto ACCEL_WEIGHT = 0.02f;
|
constexpr auto ACCEL_WEIGHT = 0.02f;
|
||||||
auto const accel = imu_accelerometer_group->GetState().value_or(Common::Vec3{});
|
auto const accel = imu_accelerometer_group->GetState().value_or(Common::Vec3{});
|
||||||
if (accel.LengthSquared())
|
if (accel.LengthSquared())
|
||||||
state->rotation = ComplementaryFilter(accel, state->rotation, ACCEL_WEIGHT);
|
state->rotation = ComplementaryFilter(state->rotation, accel, ACCEL_WEIGHT);
|
||||||
|
|
||||||
const auto inv_rotation = state->rotation.Inverted();
|
|
||||||
|
|
||||||
// Clamp yaw within configured bounds.
|
// 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);
|
const auto max_yaw = float(imu_ir_group->GetTotalYaw() / 2);
|
||||||
auto target_yaw = std::clamp(yaw, -max_yaw, max_yaw);
|
auto target_yaw = std::clamp(yaw, -max_yaw, max_yaw);
|
||||||
|
|
||||||
// Handle the "Recenter" button being pressed.
|
// Handle the "Recenter" button being pressed.
|
||||||
if (imu_ir_group->controls[0]->GetState<bool>())
|
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;
|
target_yaw = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -390,10 +386,33 @@ Common::Matrix33 GetMatrixFromAcceleration(const Common::Vec3& accel)
|
|||||||
axis.LengthSquared() ? axis.Normalized() : Common::Vec3{0, 1, 0});
|
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)
|
Common::Matrix33 GetRotationalMatrix(const Common::Vec3& angle)
|
||||||
{
|
{
|
||||||
return Common::Matrix33::RotateZ(angle.z) * Common::Matrix33::RotateY(angle.y) *
|
return Common::Matrix33::RotateZ(angle.z) * Common::Matrix33::RotateY(angle.y) *
|
||||||
Common::Matrix33::RotateX(angle.x);
|
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
|
} // 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.
|
// Estimate orientation from accelerometer data.
|
||||||
Common::Matrix33 GetMatrixFromAcceleration(const Common::Vec3& accel);
|
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.
|
// Build a rotational matrix from euler angles.
|
||||||
Common::Matrix33 GetRotationalMatrix(const Common::Vec3& angle);
|
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,
|
void ApproachPositionWithJerk(PositionalState* state, const Common::Vec3& target,
|
||||||
const Common::Vec3& max_jerk, float time_elapsed);
|
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);
|
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).
|
// 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,
|
WiimoteCommon::AccelData ConvertAccelData(const Common::Vec3& accel, u16 zero_g, u16 one_g);
|
||||||
u16 one_g);
|
|
||||||
|
|
||||||
} // namespace WiimoteEmu
|
} // namespace WiimoteEmu
|
||||||
|
@ -236,10 +236,6 @@ void Wiimote::HandleRequestStatus(const OutputReportRequestStatus&)
|
|||||||
// Update status struct
|
// Update status struct
|
||||||
m_status.extension = m_extension_port.IsDeviceConnected();
|
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));
|
m_status.battery = u8(std::lround(m_battery_setting.GetValue() / 100 * MAX_BATTERY_LEVEL));
|
||||||
|
|
||||||
if (Core::WantsDeterminism())
|
if (Core::WantsDeterminism())
|
||||||
|
@ -114,8 +114,10 @@ void Classic::Update()
|
|||||||
{
|
{
|
||||||
const ControllerEmu::AnalogStick::StateData left_stick_state = m_left_stick->GetState();
|
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));
|
const u8 x = 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 y = static_cast<u8>(LEFT_STICK_CENTER + (left_stick_state.y * LEFT_STICK_RADIUS));
|
||||||
|
|
||||||
|
classic_data.SetLeftStick({x, y});
|
||||||
}
|
}
|
||||||
|
|
||||||
// right stick
|
// 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 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));
|
const u8 y = static_cast<u8>(RIGHT_STICK_CENTER + (right_stick_data.y * RIGHT_STICK_RADIUS));
|
||||||
|
|
||||||
classic_data.rx1 = x;
|
classic_data.SetRightStick({x, y});
|
||||||
classic_data.rx2 = x >> 1;
|
|
||||||
classic_data.rx3 = x >> 3;
|
|
||||||
classic_data.ry = y;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// triggers
|
// triggers
|
||||||
@ -139,18 +138,15 @@ void Classic::Update()
|
|||||||
const u8 lt = static_cast<u8>(trigs[0] * TRIGGER_RANGE);
|
const u8 lt = static_cast<u8>(trigs[0] * TRIGGER_RANGE);
|
||||||
const u8 rt = static_cast<u8>(trigs[1] * TRIGGER_RANGE);
|
const u8 rt = static_cast<u8>(trigs[1] * TRIGGER_RANGE);
|
||||||
|
|
||||||
classic_data.lt1 = lt;
|
classic_data.SetLeftTrigger(lt);
|
||||||
classic_data.lt2 = lt >> 3;
|
classic_data.SetRightTrigger(rt);
|
||||||
classic_data.rt = rt;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// buttons
|
// buttons and dpad
|
||||||
m_buttons->GetState(&classic_data.bt.hex, classic_button_bitmasks.data());
|
u16 buttons = 0;
|
||||||
// dpad
|
m_buttons->GetState(&buttons, classic_button_bitmasks.data());
|
||||||
m_dpad->GetState(&classic_data.bt.hex, classic_dpad_bitmasks.data());
|
m_dpad->GetState(&buttons, classic_dpad_bitmasks.data());
|
||||||
|
classic_data.SetButtons(buttons);
|
||||||
// flip button bits
|
|
||||||
classic_data.bt.hex ^= 0xFFFF;
|
|
||||||
|
|
||||||
Common::BitCastPtr<DataFormat>(&m_reg.controller_data) = classic_data;
|
Common::BitCastPtr<DataFormat>(&m_reg.controller_data) = classic_data;
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,9 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
|
#include "Common/Matrix.h"
|
||||||
#include "Core/HW/WiimoteCommon/WiimoteReport.h"
|
#include "Core/HW/WiimoteCommon/WiimoteReport.h"
|
||||||
#include "Core/HW/WiimoteEmu/Extension/Extension.h"
|
#include "Core/HW/WiimoteEmu/Extension/Extension.h"
|
||||||
|
|
||||||
@ -56,12 +59,59 @@ public:
|
|||||||
};
|
};
|
||||||
static_assert(sizeof(ButtonFormat) == 2, "Wrong size");
|
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
|
struct DataFormat
|
||||||
{
|
{
|
||||||
// lx/ly/lz; left joystick
|
using StickType = Common::TVec2<u8>;
|
||||||
// rx/ry/rz; right joystick
|
using LeftStickRawValue = ControllerEmu::RawValue<StickType, LEFT_STICK_BITS>;
|
||||||
// lt; left trigger
|
using RightStickRawValue = ControllerEmu::RawValue<StickType, RIGHT_STICK_BITS>;
|
||||||
// rt; left trigger
|
|
||||||
|
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 lx : 6; // byte 0
|
||||||
u8 rx3 : 2;
|
u8 rx3 : 2;
|
||||||
@ -80,6 +130,53 @@ public:
|
|||||||
};
|
};
|
||||||
static_assert(sizeof(DataFormat) == 6, "Wrong size");
|
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();
|
Classic();
|
||||||
|
|
||||||
void Update() override;
|
void Update() override;
|
||||||
@ -110,13 +207,10 @@ public:
|
|||||||
|
|
||||||
static constexpr u8 CAL_STICK_CENTER = 0x80;
|
static constexpr u8 CAL_STICK_CENTER = 0x80;
|
||||||
static constexpr u8 CAL_STICK_RANGE = 0x7f;
|
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_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 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_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);
|
static constexpr u8 RIGHT_STICK_RADIUS = CAL_STICK_RANGE >> (CAL_STICK_BITS - RIGHT_STICK_BITS);
|
||||||
|
|
||||||
|
@ -87,10 +87,9 @@ void Nunchuk::Update()
|
|||||||
}
|
}
|
||||||
|
|
||||||
// buttons
|
// buttons
|
||||||
m_buttons->GetState(&nc_data.bt.hex, nunchuk_button_bitmasks.data());
|
u8 buttons = 0;
|
||||||
|
m_buttons->GetState(&buttons, nunchuk_button_bitmasks.data());
|
||||||
// flip the button bits :/
|
nc_data.SetButtons(buttons);
|
||||||
nc_data.bt.hex ^= 0x03;
|
|
||||||
|
|
||||||
// Acceleration data:
|
// Acceleration data:
|
||||||
EmulateSwing(&m_swing_state, m_swing, 1.f / ::Wiimote::UPDATE_FREQ);
|
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.
|
// 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);
|
const auto acc = ConvertAccelData(accel, ACCEL_ZERO_G << 2, ACCEL_ONE_G << 2);
|
||||||
|
nc_data.SetAccel(acc.value);
|
||||||
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;
|
|
||||||
|
|
||||||
Common::BitCastPtr<DataFormat>(&m_reg.controller_data) = nc_data;
|
Common::BitCastPtr<DataFormat>(&m_reg.controller_data) = nc_data;
|
||||||
}
|
}
|
||||||
|
@ -51,25 +51,103 @@ public:
|
|||||||
};
|
};
|
||||||
static_assert(sizeof(ButtonFormat) == 1, "Wrong size");
|
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
|
ax = val >> 2;
|
||||||
u8 jx;
|
bt.acc_x_lsb = val & 0b11;
|
||||||
u8 jy;
|
}
|
||||||
|
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 GetButtons() const
|
||||||
u8 ax;
|
{
|
||||||
u8 ay;
|
// 0 == pressed.
|
||||||
u8 az;
|
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
|
// joystick x, y
|
||||||
ButtonFormat bt;
|
u8 jx;
|
||||||
};
|
u8 jy;
|
||||||
|
|
||||||
|
// accelerometer
|
||||||
|
u8 ax;
|
||||||
|
u8 ay;
|
||||||
|
u8 az;
|
||||||
|
|
||||||
|
// buttons + accelerometer LSBs
|
||||||
|
ButtonFormat bt;
|
||||||
};
|
};
|
||||||
static_assert(sizeof(DataFormat) == 6, "Wrong size");
|
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();
|
Nunchuk();
|
||||||
|
|
||||||
void Update() override;
|
void Update() override;
|
||||||
|
@ -16,7 +16,6 @@
|
|||||||
#include "Common/Logging/Log.h"
|
#include "Common/Logging/Log.h"
|
||||||
#include "Common/MathUtil.h"
|
#include "Common/MathUtil.h"
|
||||||
#include "Common/MsgHandler.h"
|
#include "Common/MsgHandler.h"
|
||||||
#include "Common/Swap.h"
|
|
||||||
|
|
||||||
#include "Core/HW/Wiimote.h"
|
#include "Core/HW/Wiimote.h"
|
||||||
#include "Core/HW/WiimoteEmu/Dynamics.h"
|
#include "Core/HW/WiimoteEmu/Dynamics.h"
|
||||||
@ -56,6 +55,41 @@ struct MPI : mbedtls_mpi
|
|||||||
|
|
||||||
namespace WiimoteEmu
|
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")
|
MotionPlus::MotionPlus() : Extension("MotionPlus")
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@ -82,35 +116,20 @@ void MotionPlus::Reset()
|
|||||||
constexpr u16 ROLL_SCALE = CALIBRATION_ZERO + CALIBRATION_SCALE_OFFSET;
|
constexpr u16 ROLL_SCALE = CALIBRATION_ZERO + CALIBRATION_SCALE_OFFSET;
|
||||||
constexpr u16 PITCH_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(sizeof(CalibrationData) == 0x20, "Bad size.");
|
||||||
|
|
||||||
static_assert(CALIBRATION_FAST_SCALE_DEGREES % 6 == 0, "Value should be divisible by 6.");
|
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.");
|
static_assert(CALIBRATION_SLOW_SCALE_DEGREES % 6 == 0, "Value should be divisible by 6.");
|
||||||
|
|
||||||
CalibrationData calibration;
|
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.fast.degrees_div_6 = CALIBRATION_FAST_SCALE_DEGREES / 6;
|
||||||
calibration.slow.degrees_div_6 = CALIBRATION_SLOW_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_1 = 0x0b;
|
||||||
calibration.uid_2 = 0xe9;
|
calibration.uid_2 = 0xe9;
|
||||||
|
|
||||||
// Update checksum (crc32 of all data other than the checksum itself):
|
calibration.UpdateChecksum();
|
||||||
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);
|
|
||||||
|
|
||||||
Common::BitCastPtr<CalibrationData>(m_reg_data.calibration_data.data()) = calibration;
|
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)
|
void MotionPlus::DoState(PointerWrap& p)
|
||||||
{
|
{
|
||||||
p.Do(m_reg_data);
|
p.Do(m_reg_data);
|
||||||
@ -547,47 +571,10 @@ void MotionPlus::PrepareInput(const Common::Vec3& angular_velocity)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case PassthroughMode::Nunchuk:
|
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:
|
case PassthroughMode::Classic:
|
||||||
{
|
|
||||||
if (EXT_AMT == m_i2c_bus.BusRead(EXT_SLAVE, EXT_ADDR, EXT_AMT, data))
|
if (EXT_AMT == m_i2c_bus.BusRead(EXT_SLAVE, EXT_ADDR, EXT_AMT, data))
|
||||||
{
|
{
|
||||||
// Passthrough data modifications via wiibrew.org
|
ApplyPassthroughModifications(GetPassthroughMode(), data);
|
||||||
// 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));
|
|
||||||
|
|
||||||
mplus_data = Common::BitCastPtr<DataFormat>(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.
|
// 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;
|
mplus_data.is_mp_data = true;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
// This really shouldn't happen as the M+ deactivates on an invalid mode write.
|
// This really shouldn't happen as the M+ deactivates on an invalid mode write.
|
||||||
ERROR_LOG(WIIMOTE, "M+ unknown passthrough-mode %d", int(GetPassthroughMode()));
|
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;
|
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
|
} // namespace WiimoteEmu
|
||||||
|
@ -7,59 +7,90 @@
|
|||||||
#include <array>
|
#include <array>
|
||||||
|
|
||||||
#include "Common/CommonTypes.h"
|
#include "Common/CommonTypes.h"
|
||||||
|
#include "Common/Swap.h"
|
||||||
#include "Core/HW/WiimoteEmu/Dynamics.h"
|
#include "Core/HW/WiimoteEmu/Dynamics.h"
|
||||||
#include "Core/HW/WiimoteEmu/ExtensionPort.h"
|
#include "Core/HW/WiimoteEmu/ExtensionPort.h"
|
||||||
#include "Core/HW/WiimoteEmu/I2CBus.h"
|
#include "Core/HW/WiimoteEmu/I2CBus.h"
|
||||||
|
|
||||||
namespace WiimoteEmu
|
namespace WiimoteEmu
|
||||||
{
|
{
|
||||||
struct AngularVelocity;
|
|
||||||
|
|
||||||
struct MotionPlus : public Extension
|
struct MotionPlus : public Extension
|
||||||
{
|
{
|
||||||
public:
|
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
|
enum class PassthroughMode : u8
|
||||||
{
|
{
|
||||||
|
// Note: `Disabled` is an M+ enabled with no passthrough. Maybe there is a better name.
|
||||||
Disabled = 0x04,
|
Disabled = 0x04,
|
||||||
Nunchuk = 0x05,
|
Nunchuk = 0x05,
|
||||||
Classic = 0x07,
|
Classic = 0x07,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class ActivationStatus
|
#pragma pack(push, 1)
|
||||||
|
struct CalibrationBlock
|
||||||
{
|
{
|
||||||
Inactive,
|
Common::BigEndianValue<u16> yaw_zero;
|
||||||
Activating,
|
Common::BigEndianValue<u16> roll_zero;
|
||||||
Deactivating,
|
Common::BigEndianValue<u16> pitch_zero;
|
||||||
Active,
|
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
|
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
|
// yaw1, roll1, pitch1: Bits 0-7
|
||||||
// yaw2, roll2, pitch2: Bits 8-13
|
// yaw2, roll2, pitch2: Bits 8-13
|
||||||
|
|
||||||
@ -79,7 +110,50 @@ private:
|
|||||||
u8 is_mp_data : 1;
|
u8 is_mp_data : 1;
|
||||||
u8 pitch2 : 6;
|
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
|
struct Register
|
||||||
{
|
{
|
||||||
std::array<u8, 21> controller_data;
|
std::array<u8, 21> controller_data;
|
||||||
@ -135,14 +209,8 @@ private:
|
|||||||
std::array<u8, 6> ext_identifier;
|
std::array<u8, 6> ext_identifier;
|
||||||
};
|
};
|
||||||
#pragma pack(pop)
|
#pragma pack(pop)
|
||||||
static_assert(sizeof(DataFormat) == 6, "Wrong size");
|
|
||||||
static_assert(0x100 == sizeof(Register), "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 int CALIBRATION_BITS = 16;
|
||||||
|
|
||||||
static constexpr u16 CALIBRATION_ZERO = 1 << (CALIBRATION_BITS - 1);
|
static constexpr u16 CALIBRATION_ZERO = 1 << (CALIBRATION_BITS - 1);
|
||||||
|
@ -500,7 +500,7 @@ void Wiimote::SendDataReport()
|
|||||||
if (rpt_builder.HasAccel())
|
if (rpt_builder.HasAccel())
|
||||||
{
|
{
|
||||||
// Calibration values are 8-bit but we want 10-bit precision, so << 2.
|
// 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);
|
ConvertAccelData(GetTotalAcceleration(), ACCEL_ZERO_G << 2, ACCEL_ONE_G << 2);
|
||||||
rpt_builder.SetAccelData(accel);
|
rpt_builder.SetAccelData(accel);
|
||||||
}
|
}
|
||||||
|
@ -257,12 +257,13 @@ void WiimoteDarwin::DisablePowerAssertionInternal()
|
|||||||
@implementation SearchBT
|
@implementation SearchBT
|
||||||
- (void)deviceInquiryComplete:(IOBluetoothDeviceInquiry*)sender
|
- (void)deviceInquiryComplete:(IOBluetoothDeviceInquiry*)sender
|
||||||
error:(IOReturn)error
|
error:(IOReturn)error
|
||||||
aborted:(BOOL)aborted {
|
aborted:(BOOL)aborted
|
||||||
|
{
|
||||||
done = true;
|
done = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)deviceInquiryDeviceFound:(IOBluetoothDeviceInquiry*)sender
|
- (void)deviceInquiryDeviceFound:(IOBluetoothDeviceInquiry*)sender device:(IOBluetoothDevice*)device
|
||||||
device:(IOBluetoothDevice*)device {
|
{
|
||||||
NOTICE_LOG(WIIMOTE, "Discovered Bluetooth device at %s: %s", [[device addressString] UTF8String],
|
NOTICE_LOG(WIIMOTE, "Discovered Bluetooth device at %s: %s", [[device addressString] UTF8String],
|
||||||
[[device name] UTF8String]);
|
[[device name] UTF8String]);
|
||||||
|
|
||||||
@ -274,11 +275,12 @@ void WiimoteDarwin::DisablePowerAssertionInternal()
|
|||||||
@implementation ConnectBT
|
@implementation ConnectBT
|
||||||
- (void)l2capChannelData:(IOBluetoothL2CAPChannel*)l2capChannel
|
- (void)l2capChannelData:(IOBluetoothL2CAPChannel*)l2capChannel
|
||||||
data:(unsigned char*)data
|
data:(unsigned char*)data
|
||||||
length:(NSUInteger)length {
|
length:(NSUInteger)length
|
||||||
|
{
|
||||||
IOBluetoothDevice* device = [l2capChannel device];
|
IOBluetoothDevice* device = [l2capChannel device];
|
||||||
WiimoteReal::WiimoteDarwin* wm = nullptr;
|
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++)
|
for (int i = 0; i < MAX_WIIMOTES; i++)
|
||||||
{
|
{
|
||||||
@ -314,11 +316,12 @@ void WiimoteDarwin::DisablePowerAssertionInternal()
|
|||||||
CFRunLoopStop(CFRunLoopGetCurrent());
|
CFRunLoopStop(CFRunLoopGetCurrent());
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)l2capChannelClosed:(IOBluetoothL2CAPChannel*)l2capChannel {
|
- (void)l2capChannelClosed:(IOBluetoothL2CAPChannel*)l2capChannel
|
||||||
|
{
|
||||||
IOBluetoothDevice* device = [l2capChannel device];
|
IOBluetoothDevice* device = [l2capChannel device];
|
||||||
WiimoteReal::WiimoteDarwin* wm = nullptr;
|
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++)
|
for (int i = 0; i < MAX_WIIMOTES; i++)
|
||||||
{
|
{
|
||||||
|
@ -26,6 +26,7 @@
|
|||||||
#include "Core/HW/WiimoteReal/IOWin.h"
|
#include "Core/HW/WiimoteReal/IOWin.h"
|
||||||
#include "Core/HW/WiimoteReal/IOdarwin.h"
|
#include "Core/HW/WiimoteReal/IOdarwin.h"
|
||||||
#include "Core/HW/WiimoteReal/IOhidapi.h"
|
#include "Core/HW/WiimoteReal/IOhidapi.h"
|
||||||
|
#include "InputCommon/ControllerInterface/Wiimote/Wiimote.h"
|
||||||
#include "InputCommon/InputConfig.h"
|
#include "InputCommon/InputConfig.h"
|
||||||
|
|
||||||
#include "SFML/Network.hpp"
|
#include "SFML/Network.hpp"
|
||||||
@ -35,7 +36,7 @@ namespace WiimoteReal
|
|||||||
using namespace WiimoteCommon;
|
using namespace WiimoteCommon;
|
||||||
|
|
||||||
static void TryToConnectBalanceBoard(std::unique_ptr<Wiimote>);
|
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 void HandleWiimoteDisconnect(int index);
|
||||||
|
|
||||||
static bool g_real_wiimotes_initialized = false;
|
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::unordered_set<std::string> s_known_ids;
|
||||||
static std::mutex s_known_ids_mutex;
|
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.
|
// Real wii remotes assigned to a particular slot.
|
||||||
std::unique_ptr<Wiimote> g_wiimotes[MAX_BBMOTES];
|
std::unique_ptr<Wiimote> g_wiimotes[MAX_BBMOTES];
|
||||||
@ -72,22 +73,64 @@ std::vector<WiimotePoolEntry> g_wiimote_pool;
|
|||||||
|
|
||||||
WiimoteScanner g_wiimote_scanner;
|
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())
|
ciface::Wiimote::ReleaseDevices(1);
|
||||||
{
|
|
||||||
INFO_LOG(WIIMOTE, "Removing expired wiimote pool entry.");
|
// Still empty?
|
||||||
it = g_wiimote_pool.erase(it);
|
if (g_wiimote_pool.empty())
|
||||||
}
|
return;
|
||||||
else
|
|
||||||
{
|
|
||||||
++it;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
Wiimote::Wiimote() = default;
|
||||||
@ -165,7 +208,7 @@ void Wiimote::ResetDataReporting()
|
|||||||
OutputReportMode rpt = {};
|
OutputReportMode rpt = {};
|
||||||
rpt.mode = InputReportID::ReportCore;
|
rpt.mode = InputReportID::ReportCore;
|
||||||
rpt.continuous = 0;
|
rpt.continuous = 0;
|
||||||
QueueReport(OutputReportID::ReportMode, &rpt, sizeof(rpt));
|
QueueReport(rpt);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Wiimote::ClearReadQueue()
|
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) &&
|
else if (rpt[1] == u8(OutputReportID::SpeakerData) &&
|
||||||
(!SConfig::GetInstance().m_WiimoteEnableSpeaker || !m_speaker_enable || m_speaker_mute))
|
(!SConfig::GetInstance().m_WiimoteEnableSpeaker || !m_speaker_enable || m_speaker_mute))
|
||||||
{
|
{
|
||||||
|
rpt.resize(3);
|
||||||
// Translate undesired speaker data reports into rumble reports.
|
// Translate undesired speaker data reports into rumble reports.
|
||||||
rpt[1] = u8(OutputReportID::Rumble);
|
rpt[1] = u8(OutputReportID::Rumble);
|
||||||
// Keep only the rumble bit.
|
// Keep only the rumble bit.
|
||||||
rpt[2] &= 0x1;
|
rpt[2] &= 0x1;
|
||||||
rpt.resize(3);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
WriteReport(std::move(rpt));
|
WriteReport(std::move(rpt));
|
||||||
@ -380,11 +423,16 @@ static bool IsDataReport(const Report& rpt)
|
|||||||
return rpt.size() >= 2 && rpt[1] >= u8(InputReportID::ReportCore);
|
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
|
// Returns the next report that should be sent
|
||||||
Report& Wiimote::ProcessReadQueue()
|
Report& Wiimote::ProcessReadQueue()
|
||||||
{
|
{
|
||||||
// Pop through the queued reports
|
// 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))
|
if (!IsDataReport(m_last_input_report))
|
||||||
{
|
{
|
||||||
@ -452,26 +500,16 @@ void Wiimote::Prepare()
|
|||||||
|
|
||||||
bool Wiimote::PrepareOnThread()
|
bool Wiimote::PrepareOnThread()
|
||||||
{
|
{
|
||||||
// core buttons, no continuous reporting
|
// Set reporting mode to non-continuous core buttons and turn on rumble.
|
||||||
// TODO: use the structs..
|
u8 static const mode_report[] = {WR_SET_REPORT | BT_OUTPUT, u8(OutputReportID::ReportMode), 1,
|
||||||
u8 static const mode_report[] = {WR_SET_REPORT | BT_OUTPUT, u8(OutputReportID::ReportMode), 0,
|
|
||||||
u8(InputReportID::ReportCore)};
|
u8(InputReportID::ReportCore)};
|
||||||
|
|
||||||
// Set the active LEDs and turn on rumble.
|
// Request status and turn off 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
|
|
||||||
u8 static const req_status_report[] = {WR_SET_REPORT | BT_OUTPUT,
|
u8 static const req_status_report[] = {WR_SET_REPORT | BT_OUTPUT,
|
||||||
u8(OutputReportID::RequestStatus), 0};
|
u8(OutputReportID::RequestStatus), 0};
|
||||||
// TODO: check for sane response?
|
|
||||||
|
|
||||||
return (IOWrite(mode_report, sizeof(mode_report)) && IOWrite(led_report, sizeof(led_report)) &&
|
return IOWrite(mode_report, sizeof(mode_report)) &&
|
||||||
(Common::SleepCurrentThread(200), IOWrite(rumble_report, sizeof(rumble_report))) &&
|
(Common::SleepCurrentThread(200), IOWrite(req_status_report, sizeof(req_status_report)));
|
||||||
IOWrite(req_status_report, sizeof(req_status_report)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Wiimote::EmuStart()
|
void Wiimote::EmuStart()
|
||||||
@ -499,32 +537,20 @@ void Wiimote::EmuPause()
|
|||||||
DisablePowerAssertionInternal();
|
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()
|
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
|
// Figure out how many real Wiimotes are required
|
||||||
unsigned int wanted_wiimotes = 0;
|
unsigned int wanted_wiimotes = 0;
|
||||||
for (unsigned int i = 0; i < MAX_WIIMOTES; ++i)
|
for (unsigned int i = 0; i < MAX_WIIMOTES; ++i)
|
||||||
if (WiimoteCommon::GetSource(i) == WiimoteSource::Real && !g_wiimotes[i])
|
if (WiimoteCommon::GetSource(i) == WiimoteSource::Real && !g_wiimotes[i])
|
||||||
++wanted_wiimotes;
|
++wanted_wiimotes;
|
||||||
|
|
||||||
return wanted_wiimotes;
|
return wanted_wiimotes;
|
||||||
}
|
}
|
||||||
|
|
||||||
static unsigned int CalculateWantedBB()
|
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;
|
unsigned int wanted_bb = 0;
|
||||||
if (WiimoteCommon::GetSource(WIIMOTE_BALANCE_BOARD) == WiimoteSource::Real &&
|
if (WiimoteCommon::GetSource(WIIMOTE_BALANCE_BOARD) == WiimoteSource::Real &&
|
||||||
!g_wiimotes[WIIMOTE_BALANCE_BOARD])
|
!g_wiimotes[WIIMOTE_BALANCE_BOARD])
|
||||||
@ -564,14 +590,63 @@ bool WiimoteScanner::IsReady() const
|
|||||||
|
|
||||||
static void CheckForDisconnectedWiimotes()
|
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)
|
for (unsigned int i = 0; i < MAX_BBMOTES; ++i)
|
||||||
if (g_wiimotes[i] && !g_wiimotes[i]->IsConnected())
|
if (g_wiimotes[i] && !g_wiimotes[i]->IsConnected())
|
||||||
HandleWiimoteDisconnect(i);
|
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()
|
void WiimoteScanner::ThreadFunc()
|
||||||
{
|
{
|
||||||
|
std::thread pool_thread(&WiimoteScanner::PoolThreadFunc, this);
|
||||||
|
|
||||||
Common::SetCurrentThreadName("Wiimote Scanning Thread");
|
Common::SetCurrentThreadName("Wiimote Scanning Thread");
|
||||||
|
|
||||||
NOTICE_LOG(WIIMOTE, "Wiimote scanning thread has started.");
|
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));
|
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();
|
CheckForDisconnectedWiimotes();
|
||||||
|
|
||||||
if (m_scan_mode.load() == WiimoteScanMode::DO_NOT_SCAN)
|
if (m_scan_mode.load() == WiimoteScanMode::DO_NOT_SCAN)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (!g_real_wiimotes_initialized)
|
// If we don't want Wiimotes in ControllerInterface, we may not need them at all.
|
||||||
continue;
|
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
|
// We don't want any remotes if we already connected everything we need.
|
||||||
for (const auto& backend : m_backends)
|
if (0 == CalculateWantedWiimotes() && 0 == CalculateWantedBB())
|
||||||
backend->Update();
|
continue;
|
||||||
|
}
|
||||||
if (0 == CalculateWantedWiimotes() && 0 == CalculateWantedBB())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
for (const auto& backend : m_backends)
|
for (const auto& backend : m_backends)
|
||||||
{
|
{
|
||||||
@ -617,7 +698,7 @@ void WiimoteScanner::ThreadFunc()
|
|||||||
Wiimote* found_board = nullptr;
|
Wiimote* found_board = nullptr;
|
||||||
backend->FindWiimotes(found_wiimotes, found_board);
|
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)
|
for (auto* wiimote : found_wiimotes)
|
||||||
{
|
{
|
||||||
@ -626,7 +707,8 @@ void WiimoteScanner::ThreadFunc()
|
|||||||
s_known_ids.insert(wiimote->GetId());
|
s_known_ids.insert(wiimote->GetId());
|
||||||
}
|
}
|
||||||
|
|
||||||
TryToConnectWiimote(std::unique_ptr<Wiimote>(wiimote));
|
AddWiimoteToPool(std::unique_ptr<Wiimote>(wiimote));
|
||||||
|
ProcessWiimotePool();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (found_board)
|
if (found_board)
|
||||||
@ -641,32 +723,32 @@ void WiimoteScanner::ThreadFunc()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_scan_mode.load() == WiimoteScanMode::SCAN_ONCE)
|
// Stop scanning if not in continous mode.
|
||||||
m_scan_mode.store(WiimoteScanMode::DO_NOT_SCAN);
|
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);
|
std::lock_guard<std::mutex> lg(m_backends_mutex);
|
||||||
m_backends.clear();
|
m_backends.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pool_thread.join();
|
||||||
|
|
||||||
NOTICE_LOG(WIIMOTE, "Wiimote scanning thread has stopped.");
|
NOTICE_LOG(WIIMOTE, "Wiimote scanning thread has stopped.");
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Wiimote::Connect(int index)
|
bool Wiimote::Connect(int index)
|
||||||
{
|
{
|
||||||
m_index = index;
|
m_index = index;
|
||||||
m_need_prepare.Set();
|
|
||||||
|
|
||||||
if (!m_run_thread.IsSet())
|
if (!m_run_thread.IsSet())
|
||||||
{
|
{
|
||||||
|
m_need_prepare.Set();
|
||||||
m_run_thread.Set();
|
m_run_thread.Set();
|
||||||
StartThread();
|
StartThread();
|
||||||
m_thread_ready_event.Wait();
|
m_thread_ready_event.Wait();
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
IOWakeup();
|
|
||||||
}
|
|
||||||
|
|
||||||
return IsConnected();
|
return IsConnected();
|
||||||
}
|
}
|
||||||
@ -729,6 +811,11 @@ int Wiimote::GetIndex() const
|
|||||||
return m_index;
|
return m_index;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Wiimote::SetChannel(u16 channel)
|
||||||
|
{
|
||||||
|
m_channel = channel;
|
||||||
|
}
|
||||||
|
|
||||||
void LoadSettings()
|
void LoadSettings()
|
||||||
{
|
{
|
||||||
std::string ini_filename = File::GetUserPath(D_CONFIG_IDX) + WIIMOTE_INI_NAME ".ini";
|
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();
|
g_wiimote_scanner.StartThread();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (SConfig::GetInstance().m_WiimoteContinuousScanning &&
|
if (SConfig::GetInstance().m_WiimoteContinuousScanning)
|
||||||
!SConfig::GetInstance().m_bt_passthrough_enabled)
|
|
||||||
g_wiimote_scanner.SetScanMode(WiimoteScanMode::CONTINUOUSLY_SCAN);
|
g_wiimote_scanner.SetScanMode(WiimoteScanMode::CONTINUOUSLY_SCAN);
|
||||||
else
|
else
|
||||||
g_wiimote_scanner.SetScanMode(WiimoteScanMode::DO_NOT_SCAN);
|
g_wiimote_scanner.SetScanMode(WiimoteScanMode::DO_NOT_SCAN);
|
||||||
@ -774,7 +860,7 @@ void Initialize(::Wiimote::InitializeMode init_mode)
|
|||||||
{
|
{
|
||||||
int timeout = 100;
|
int timeout = 100;
|
||||||
g_wiimote_scanner.SetScanMode(WiimoteScanMode::SCAN_ONCE);
|
g_wiimote_scanner.SetScanMode(WiimoteScanMode::SCAN_ONCE);
|
||||||
while (CalculateWantedWiimotes() > CalculateConnectedWiimotes() && timeout)
|
while (CalculateWantedWiimotes() && timeout)
|
||||||
{
|
{
|
||||||
Common::SleepCurrentThread(100);
|
Common::SleepCurrentThread(100);
|
||||||
timeout--;
|
timeout--;
|
||||||
@ -805,9 +891,13 @@ void Shutdown()
|
|||||||
|
|
||||||
NOTICE_LOG(WIIMOTE, "WiimoteReal::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)
|
for (unsigned int i = 0; i < MAX_BBMOTES; ++i)
|
||||||
HandleWiimoteDisconnect(i);
|
HandleWiimoteDisconnect(i);
|
||||||
|
|
||||||
|
// Release remotes from ControllerInterface and empty the pool.
|
||||||
|
ciface::Wiimote::ReleaseDevices();
|
||||||
|
g_wiimote_pool.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Resume()
|
void Resume()
|
||||||
@ -836,6 +926,13 @@ static bool TryToConnectWiimoteToSlot(std::unique_ptr<Wiimote>& wm, unsigned int
|
|||||||
return false;
|
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);
|
g_wiimotes[i] = std::move(wm);
|
||||||
Core::RunAsCPUThread([i] { ::Wiimote::Connect(i, true); });
|
Core::RunAsCPUThread([i] { ::Wiimote::Connect(i, true); });
|
||||||
|
|
||||||
@ -844,22 +941,6 @@ static bool TryToConnectWiimoteToSlot(std::unique_ptr<Wiimote>& wm, unsigned int
|
|||||||
return true;
|
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)
|
static void TryToConnectBalanceBoard(std::unique_ptr<Wiimote> wm)
|
||||||
{
|
{
|
||||||
if (TryToConnectWiimoteToSlot(wm, WIIMOTE_BALANCE_BOARD))
|
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)
|
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])
|
if (g_wiimotes[wiimote_number])
|
||||||
g_wiimotes[wiimote_number]->InterruptChannel(channel_id, data, size);
|
g_wiimotes[wiimote_number]->InterruptChannel(channel_id, data, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ControlChannel(int wiimote_number, u16 channel_id, const void* data, u32 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])
|
if (g_wiimotes[wiimote_number])
|
||||||
g_wiimotes[wiimote_number]->ControlChannel(channel_id, data, size);
|
g_wiimotes[wiimote_number]->ControlChannel(channel_id, data, size);
|
||||||
}
|
}
|
||||||
@ -946,25 +1027,17 @@ bool IsNewWiimote(const std::string& identifier)
|
|||||||
|
|
||||||
void HandleWiimoteSourceChange(unsigned int index)
|
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]))
|
||||||
{
|
AddWiimoteToPool(std::move(removed_wiimote));
|
||||||
if (auto removed_wiimote = std::move(g_wiimotes[index]))
|
|
||||||
{
|
ProcessWiimotePool();
|
||||||
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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}; // namespace WiimoteReal
|
|
||||||
|
void HandleWiimotesInControllerInterfaceSettingChange()
|
||||||
|
{
|
||||||
|
ProcessWiimotePool();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace WiimoteReal
|
||||||
|
@ -65,6 +65,7 @@ public:
|
|||||||
void Update();
|
void Update();
|
||||||
bool CheckForButtonPress();
|
bool CheckForButtonPress();
|
||||||
|
|
||||||
|
bool GetNextReport(Report* report);
|
||||||
Report& ProcessReadQueue();
|
Report& ProcessReadQueue();
|
||||||
|
|
||||||
void Read();
|
void Read();
|
||||||
@ -101,8 +102,16 @@ public:
|
|||||||
|
|
||||||
void QueueReport(WiimoteCommon::OutputReportID rpt_id, const void* data, unsigned int size);
|
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;
|
int GetIndex() const;
|
||||||
|
|
||||||
|
void SetChannel(u16 channel);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Wiimote();
|
Wiimote();
|
||||||
|
|
||||||
@ -173,6 +182,7 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
void ThreadFunc();
|
void ThreadFunc();
|
||||||
|
void PoolThreadFunc();
|
||||||
|
|
||||||
std::vector<std::unique_ptr<WiimoteScannerBackend>> m_backends;
|
std::vector<std::unique_ptr<WiimoteScannerBackend>> m_backends;
|
||||||
mutable std::mutex m_backends_mutex;
|
mutable std::mutex m_backends_mutex;
|
||||||
@ -183,10 +193,13 @@ private:
|
|||||||
std::atomic<WiimoteScanMode> m_scan_mode{WiimoteScanMode::DO_NOT_SCAN};
|
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 WiimoteScanner g_wiimote_scanner;
|
||||||
extern std::unique_ptr<Wiimote> g_wiimotes[MAX_BBMOTES];
|
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 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 ControlChannel(int wiimote_number, u16 channel_id, const void* data, u32 size);
|
||||||
void Update(int wiimote_number);
|
void Update(int wiimote_number);
|
||||||
@ -202,4 +215,7 @@ void HandleWiimoteSourceChange(unsigned int wiimote_number);
|
|||||||
void InitAdapterClass();
|
void InitAdapterClass();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
void HandleWiimotesInControllerInterfaceSettingChange();
|
||||||
|
void ProcessWiimotePool();
|
||||||
|
|
||||||
} // namespace WiimoteReal
|
} // namespace WiimoteReal
|
||||||
|
@ -678,12 +678,13 @@ static void SetWiiInputDisplayString(int remoteID, const DataReportBuilder& rpt,
|
|||||||
|
|
||||||
if (rpt.HasAccel())
|
if (rpt.HasAccel())
|
||||||
{
|
{
|
||||||
DataReportBuilder::AccelData accel_data;
|
AccelData accel_data;
|
||||||
rpt.GetAccelData(&accel_data);
|
rpt.GetAccelData(&accel_data);
|
||||||
|
|
||||||
// FYI: This will only print partial data for interleaved reports.
|
// 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())
|
if (rpt.HasIR())
|
||||||
@ -707,9 +708,8 @@ static void SetWiiInputDisplayString(int remoteID, const DataReportBuilder& rpt,
|
|||||||
key.Decrypt((u8*)&nunchuk, 0, sizeof(nunchuk));
|
key.Decrypt((u8*)&nunchuk, 0, sizeof(nunchuk));
|
||||||
nunchuk.bt.hex = nunchuk.bt.hex ^ 0x3;
|
nunchuk.bt.hex = nunchuk.bt.hex ^ 0x3;
|
||||||
|
|
||||||
const std::string accel = fmt::format(
|
const std::string accel = fmt::format(" N-ACC:{},{},{}", nunchuk.GetAccelX(),
|
||||||
" N-ACC:{},{},{}", (nunchuk.ax << 2) | nunchuk.bt.acc_x_lsb,
|
nunchuk.GetAccelY(), nunchuk.GetAccelZ());
|
||||||
(nunchuk.ay << 2) | nunchuk.bt.acc_y_lsb, (nunchuk.az << 2) | nunchuk.bt.acc_z_lsb);
|
|
||||||
|
|
||||||
if (nunchuk.bt.c)
|
if (nunchuk.bt.c)
|
||||||
display_str += " C";
|
display_str += " C";
|
||||||
@ -756,10 +756,14 @@ static void SetWiiInputDisplayString(int remoteID, const DataReportBuilder& rpt,
|
|||||||
if (cc.bt.home)
|
if (cc.bt.home)
|
||||||
display_str += " HOME";
|
display_str += " HOME";
|
||||||
|
|
||||||
display_str += Analog1DToString(cc.lt1 | (cc.lt2 << 3), " L", 31);
|
display_str += Analog1DToString(cc.GetLeftTrigger().value, " L", 31);
|
||||||
display_str += Analog1DToString(cc.rt, " R", 31);
|
display_str += Analog1DToString(cc.GetRightTrigger().value, " 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);
|
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);
|
std::lock_guard<std::mutex> guard(s_input_display_lock);
|
||||||
|
@ -72,8 +72,6 @@ ControllersWindow::ControllersWindow(QWidget* parent) : QDialog(parent)
|
|||||||
CreateMainLayout();
|
CreateMainLayout();
|
||||||
LoadSettings();
|
LoadSettings();
|
||||||
ConnectWidgets();
|
ConnectWidgets();
|
||||||
|
|
||||||
OnEmulationStateChanged(Core::GetState() != Core::State::Uninitialized);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ControllersWindow::CreateGamecubeLayout()
|
void ControllersWindow::CreateGamecubeLayout()
|
||||||
@ -157,6 +155,7 @@ void ControllersWindow::CreateWiimoteLayout()
|
|||||||
m_wiimote_continuous_scanning = new QCheckBox(tr("Continuous Scanning"));
|
m_wiimote_continuous_scanning = new QCheckBox(tr("Continuous Scanning"));
|
||||||
m_wiimote_real_balance_board = new QCheckBox(tr("Real Balance Board"));
|
m_wiimote_real_balance_board = new QCheckBox(tr("Real Balance Board"));
|
||||||
m_wiimote_speaker_data = new QCheckBox(tr("Enable Speaker Data"));
|
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->setVerticalSpacing(7);
|
||||||
m_wiimote_layout->setColumnMinimumWidth(0, GetRadioButtonIndicatorWidth() -
|
m_wiimote_layout->setColumnMinimumWidth(0, GetRadioButtonIndicatorWidth() -
|
||||||
@ -192,12 +191,14 @@ void ControllersWindow::CreateWiimoteLayout()
|
|||||||
m_wiimote_layout->addWidget(wm_button, wm_row, 3);
|
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_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_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()
|
void ControllersWindow::CreateCommonLayout()
|
||||||
@ -232,10 +233,15 @@ void ControllersWindow::ConnectWidgets()
|
|||||||
{
|
{
|
||||||
connect(m_button_box, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
connect(m_button_box, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||||
connect(&Settings::Instance(), &Settings::EmulationStateChanged, this,
|
connect(&Settings::Instance(), &Settings::EmulationStateChanged, this,
|
||||||
[=](Core::State state) { OnEmulationStateChanged(state != Core::State::Uninitialized); });
|
&ControllersWindow::UpdateDisabledWiimoteControls);
|
||||||
|
|
||||||
connect(m_wiimote_passthrough, &QRadioButton::toggled, this,
|
connect(m_wiimote_passthrough, &QRadioButton::toggled, this,
|
||||||
&ControllersWindow::OnWiimoteModeChanged);
|
&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_bg_input, &QCheckBox::toggled, this, &ControllersWindow::SaveSettings);
|
||||||
connect(m_common_configure_controller_interface, &QPushButton::clicked, this,
|
connect(m_common_configure_controller_interface, &QPushButton::clicked, this,
|
||||||
@ -259,7 +265,7 @@ void ControllersWindow::ConnectWidgets()
|
|||||||
&ControllersWindow::SaveSettings);
|
&ControllersWindow::SaveSettings);
|
||||||
connect(m_wiimote_boxes[i],
|
connect(m_wiimote_boxes[i],
|
||||||
static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
|
static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
|
||||||
&ControllersWindow::OnWiimoteTypeChanged);
|
&ControllersWindow::OnWiimoteModeChanged);
|
||||||
connect(m_wiimote_buttons[i], &QPushButton::clicked, this,
|
connect(m_wiimote_buttons[i], &QPushButton::clicked, this,
|
||||||
&ControllersWindow::OnWiimoteConfigure);
|
&ControllersWindow::OnWiimoteConfigure);
|
||||||
|
|
||||||
@ -273,45 +279,50 @@ void ControllersWindow::ConnectWidgets()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ControllersWindow::OnWiimoteModeChanged(bool passthrough)
|
void ControllersWindow::OnWiimoteModeChanged()
|
||||||
{
|
{
|
||||||
SaveSettings();
|
SaveSettings();
|
||||||
|
|
||||||
m_wiimote_sync->setEnabled(passthrough);
|
// Make sure continuous scanning setting is applied.
|
||||||
m_wiimote_reset->setEnabled(passthrough);
|
WiimoteReal::Initialize(::Wiimote::InitializeMode::DO_NOT_WAIT_FOR_WIIMOTES);
|
||||||
|
|
||||||
for (size_t i = 0; i < m_wiimote_groups.size(); i++)
|
UpdateDisabledWiimoteControls();
|
||||||
{
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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++)
|
for (size_t i = 0; i < m_wiimote_groups.size(); i++)
|
||||||
{
|
{
|
||||||
if (m_wiimote_boxes[i] == box)
|
m_wiimote_labels[i]->setEnabled(enable_emu_bt);
|
||||||
{
|
m_wiimote_boxes[i]->setEnabled(enable_emu_bt);
|
||||||
const int index = box->currentIndex();
|
|
||||||
m_wiimote_buttons[i]->setEnabled(index != 0 && index != 2);
|
const bool is_emu_wiimote = m_wiimote_boxes[i]->currentIndex() == 1;
|
||||||
return;
|
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)
|
void ControllersWindow::OnGCTypeChanged(int type)
|
||||||
@ -375,30 +386,6 @@ void ControllersWindow::OnWiimoteRefreshPressed()
|
|||||||
WiimoteReal::Refresh();
|
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()
|
void ControllersWindow::OnGCPadConfigure()
|
||||||
{
|
{
|
||||||
size_t index;
|
size_t index;
|
||||||
@ -489,14 +476,12 @@ void ControllersWindow::LoadSettings()
|
|||||||
m_gc_controller_boxes[i]->setCurrentIndex(*gc_index);
|
m_gc_controller_boxes[i]->setCurrentIndex(*gc_index);
|
||||||
m_gc_buttons[i]->setEnabled(*gc_index != 0 && *gc_index != 6);
|
m_gc_buttons[i]->setEnabled(*gc_index != 0 && *gc_index != 6);
|
||||||
}
|
}
|
||||||
|
m_wiimote_boxes[i]->setCurrentIndex(int(WiimoteCommon::GetSource(u32(i))));
|
||||||
const WiimoteSource source = WiimoteCommon::GetSource(int(i));
|
|
||||||
m_wiimote_boxes[i]->setCurrentIndex(int(source));
|
|
||||||
m_wiimote_buttons[i]->setEnabled(source == WiimoteSource::Emulated);
|
|
||||||
}
|
}
|
||||||
m_wiimote_real_balance_board->setChecked(WiimoteCommon::GetSource(WIIMOTE_BALANCE_BOARD) ==
|
m_wiimote_real_balance_board->setChecked(WiimoteCommon::GetSource(WIIMOTE_BALANCE_BOARD) ==
|
||||||
WiimoteSource::Real);
|
WiimoteSource::Real);
|
||||||
m_wiimote_speaker_data->setChecked(SConfig::GetInstance().m_WiimoteEnableSpeaker);
|
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_wiimote_continuous_scanning->setChecked(SConfig::GetInstance().m_WiimoteContinuousScanning);
|
||||||
|
|
||||||
m_common_bg_input->setChecked(SConfig::GetInstance().m_BackgroundInput);
|
m_common_bg_input->setChecked(SConfig::GetInstance().m_BackgroundInput);
|
||||||
@ -506,12 +491,13 @@ void ControllersWindow::LoadSettings()
|
|||||||
else
|
else
|
||||||
m_wiimote_emu->setChecked(true);
|
m_wiimote_emu->setChecked(true);
|
||||||
|
|
||||||
OnWiimoteModeChanged(SConfig::GetInstance().m_bt_passthrough_enabled);
|
OnWiimoteModeChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ControllersWindow::SaveSettings()
|
void ControllersWindow::SaveSettings()
|
||||||
{
|
{
|
||||||
SConfig::GetInstance().m_WiimoteEnableSpeaker = m_wiimote_speaker_data->isChecked();
|
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_WiimoteContinuousScanning = m_wiimote_continuous_scanning->isChecked();
|
||||||
SConfig::GetInstance().m_bt_passthrough_enabled = m_wiimote_passthrough->isChecked();
|
SConfig::GetInstance().m_bt_passthrough_enabled = m_wiimote_passthrough->isChecked();
|
||||||
SConfig::GetInstance().m_BackgroundInput = m_common_bg_input->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++)
|
for (size_t i = 0; i < m_wiimote_groups.size(); i++)
|
||||||
{
|
{
|
||||||
const auto source = WiimoteSource(m_wiimote_boxes[i]->currentIndex());
|
const int index = m_wiimote_boxes[i]->currentIndex();
|
||||||
m_wiimote_buttons[i]->setEnabled(source == WiimoteSource::Emulated);
|
WiimoteCommon::SetSource(u32(i), WiimoteSource(index));
|
||||||
WiimoteCommon::SetSource(static_cast<u32>(i), source);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
UICommon::SaveWiimoteSources();
|
UICommon::SaveWiimoteSources();
|
||||||
|
@ -27,9 +27,8 @@ public:
|
|||||||
explicit ControllersWindow(QWidget* parent);
|
explicit ControllersWindow(QWidget* parent);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void OnEmulationStateChanged(bool running);
|
void OnWiimoteModeChanged();
|
||||||
void OnWiimoteModeChanged(bool passthrough);
|
void UpdateDisabledWiimoteControls();
|
||||||
void OnWiimoteTypeChanged(int state);
|
|
||||||
void OnGCTypeChanged(int state);
|
void OnGCTypeChanged(int state);
|
||||||
void SaveSettings();
|
void SaveSettings();
|
||||||
void OnBluetoothPassthroughSyncPressed();
|
void OnBluetoothPassthroughSyncPressed();
|
||||||
@ -72,6 +71,7 @@ private:
|
|||||||
QCheckBox* m_wiimote_continuous_scanning;
|
QCheckBox* m_wiimote_continuous_scanning;
|
||||||
QCheckBox* m_wiimote_real_balance_board;
|
QCheckBox* m_wiimote_real_balance_board;
|
||||||
QCheckBox* m_wiimote_speaker_data;
|
QCheckBox* m_wiimote_speaker_data;
|
||||||
|
QCheckBox* m_wiimote_ciface;
|
||||||
QPushButton* m_wiimote_refresh;
|
QPushButton* m_wiimote_refresh;
|
||||||
|
|
||||||
// Common
|
// Common
|
||||||
|
@ -159,10 +159,12 @@ bool IsCalibrationDataSensible(const ControllerEmu::ReshapableInput::Calibration
|
|||||||
// Even the GC controller's small range would pass this test.
|
// Even the GC controller's small range would pass this test.
|
||||||
constexpr double REASONABLE_AVERAGE_RADIUS = 0.6;
|
constexpr double REASONABLE_AVERAGE_RADIUS = 0.6;
|
||||||
|
|
||||||
const double sum = std::accumulate(data.begin(), data.end(), 0.0);
|
MathUtil::RunningVariance<ControlState> stats;
|
||||||
const double mean = sum / data.size();
|
|
||||||
|
|
||||||
if (mean < REASONABLE_AVERAGE_RADIUS)
|
for (auto& x : data)
|
||||||
|
stats.Push(x);
|
||||||
|
|
||||||
|
if (stats.Mean() < REASONABLE_AVERAGE_RADIUS)
|
||||||
{
|
{
|
||||||
return false;
|
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.
|
// Approx. deviation of a square input gate, anything much more than that would be unusual.
|
||||||
constexpr double REASONABLE_DEVIATION = 0.14;
|
constexpr double REASONABLE_DEVIATION = 0.14;
|
||||||
|
|
||||||
// Population standard deviation.
|
return stats.StandardDeviation() < REASONABLE_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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Used to test for a miscalibrated stick so the user can be informed.
|
// 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.setBrush(Qt::NoBrush);
|
||||||
p.drawEllipse(QPointF{}, scale * SPHERE_SIZE, scale * SPHERE_SIZE);
|
p.drawEllipse(QPointF{}, scale * SPHERE_SIZE, scale * SPHERE_SIZE);
|
||||||
|
|
||||||
// Red dot upright target.
|
p.setPen(Qt::NoPen);
|
||||||
p.setPen(QPen(GetAdjustedInputColor(), INPUT_DOT_RADIUS / 2));
|
|
||||||
p.drawEllipse(QPointF{0, SPHERE_INDICATOR_DIST} * scale, INPUT_DOT_RADIUS, INPUT_DOT_RADIUS);
|
|
||||||
|
|
||||||
// Red dot.
|
// Red dot.
|
||||||
const auto point = rotation * Common::Vec3{0, 0, SPHERE_INDICATOR_DIST};
|
const auto point = rotation * Common::Vec3{0, 0, SPHERE_INDICATOR_DIST};
|
||||||
if (point.y > 0 || Common::Vec2(point.x, point.z).Length() > SPHERE_SIZE)
|
if (point.y > 0 || Common::Vec2(point.x, point.z).Length() > SPHERE_SIZE)
|
||||||
{
|
{
|
||||||
p.setPen(Qt::NoPen);
|
|
||||||
p.setBrush(GetAdjustedInputColor());
|
p.setBrush(GetAdjustedInputColor());
|
||||||
p.drawEllipse(QPointF(point.x, point.z) * scale, INPUT_DOT_RADIUS, INPUT_DOT_RADIUS);
|
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.
|
// Blue dot.
|
||||||
const auto point2 = -point;
|
const auto point2 = -point;
|
||||||
if (point2.y > 0 || Common::Vec2(point2.x, point2.z).Length() > SPHERE_SIZE)
|
if (point2.y > 0 || Common::Vec2(point2.x, point2.z).Length() > SPHERE_SIZE)
|
||||||
{
|
{
|
||||||
p.setPen(Qt::NoPen);
|
|
||||||
p.setBrush(Qt::blue);
|
p.setBrush(Qt::blue);
|
||||||
p.drawEllipse(QPointF(point2.x, point2.z) * scale, INPUT_DOT_RADIUS, INPUT_DOT_RADIUS);
|
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.
|
// Only draw g-force text if acceleration data is present.
|
||||||
if (!accel_state.has_value())
|
if (!accel_state.has_value())
|
||||||
return;
|
return;
|
||||||
@ -802,16 +801,18 @@ GyroMappingIndicator::GyroMappingIndicator(ControllerEmu::IMUGyroscope* group)
|
|||||||
void GyroMappingIndicator::paintEvent(QPaintEvent*)
|
void GyroMappingIndicator::paintEvent(QPaintEvent*)
|
||||||
{
|
{
|
||||||
const auto gyro_state = m_gyro_group.GetState();
|
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 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,
|
m_state *= WiimoteEmu::GetMatrixFromGyroscope(angular_velocity * Common::Vec3(-1, +1, -1) /
|
||||||
angular_velocity.y / INDICATOR_UPDATE_FREQ / 2,
|
INDICATOR_UPDATE_FREQ);
|
||||||
angular_velocity.z / -INDICATOR_UPDATE_FREQ / 2, 1);
|
|
||||||
|
|
||||||
// Reset orientation when stable for a bit:
|
// Reset orientation when stable for a bit:
|
||||||
constexpr u32 STABLE_RESET_STEPS = INDICATOR_UPDATE_FREQ;
|
constexpr u32 STABLE_RESET_STEPS = INDICATOR_UPDATE_FREQ;
|
||||||
// This works well with my DS4 but a potentially noisy device might not behave.
|
// Consider device stable when data (with deadzone applied) is zero.
|
||||||
const bool is_stable = angular_velocity.Length() < MathUtil::TAU / 30;
|
const bool is_stable = !angular_velocity.LengthSquared();
|
||||||
|
|
||||||
if (!is_stable)
|
if (!is_stable)
|
||||||
m_stable_steps = 0;
|
m_stable_steps = 0;
|
||||||
@ -839,10 +840,39 @@ void GyroMappingIndicator::paintEvent(QPaintEvent*)
|
|||||||
p.setRenderHint(QPainter::Antialiasing, true);
|
p.setRenderHint(QPainter::Antialiasing, true);
|
||||||
p.setRenderHint(QPainter::SmoothPixmapTransform, 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.setPen(Qt::NoPen);
|
||||||
p.setBrush(GetRawInputColor());
|
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;
|
const auto pt = rotation * point;
|
||||||
|
|
||||||
if (pt.y > 0)
|
if (pt.y > 0)
|
||||||
@ -850,49 +880,39 @@ void GyroMappingIndicator::paintEvent(QPaintEvent*)
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Sphere outline.
|
// 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.setBrush(Qt::NoBrush);
|
||||||
p.drawEllipse(QPointF{}, scale * SPHERE_SIZE, scale * SPHERE_SIZE);
|
p.drawEllipse(QPointF{}, scale * SPHERE_SIZE, scale * SPHERE_SIZE);
|
||||||
|
|
||||||
// Red dot upright target.
|
p.setPen(Qt::NoPen);
|
||||||
p.setPen(QPen(GetAdjustedInputColor(), INPUT_DOT_RADIUS / 2));
|
|
||||||
p.drawEllipse(QPointF{0, -SPHERE_INDICATOR_DIST} * scale, INPUT_DOT_RADIUS, INPUT_DOT_RADIUS);
|
|
||||||
|
|
||||||
// Red dot.
|
// Red dot.
|
||||||
const auto point = rotation * Common::Vec3{0, 0, -SPHERE_INDICATOR_DIST};
|
const auto point = rotation * Common::Vec3{0, 0, -SPHERE_INDICATOR_DIST};
|
||||||
if (point.y > 0 || Common::Vec2(point.x, point.z).Length() > SPHERE_SIZE)
|
if (point.y > 0 || Common::Vec2(point.x, point.z).Length() > SPHERE_SIZE)
|
||||||
{
|
{
|
||||||
p.setPen(Qt::NoPen);
|
|
||||||
p.setBrush(GetAdjustedInputColor());
|
p.setBrush(GetAdjustedInputColor());
|
||||||
p.drawEllipse(QPointF(point.x, point.z) * scale, INPUT_DOT_RADIUS, INPUT_DOT_RADIUS);
|
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.
|
// Blue dot.
|
||||||
const auto point2 = rotation * Common::Vec3{0, SPHERE_INDICATOR_DIST, 0};
|
const auto point2 = rotation * Common::Vec3{0, SPHERE_INDICATOR_DIST, 0};
|
||||||
if (point2.y > 0 || Common::Vec2(point2.x, point2.z).Length() > SPHERE_SIZE)
|
if (point2.y > 0 || Common::Vec2(point2.x, point2.z).Length() > SPHERE_SIZE)
|
||||||
{
|
{
|
||||||
p.setPen(Qt::NoPen);
|
|
||||||
p.setBrush(Qt::blue);
|
p.setBrush(Qt::blue);
|
||||||
p.drawEllipse(QPointF(point2.x, point2.z) * scale, INPUT_DOT_RADIUS, INPUT_DOT_RADIUS);
|
p.drawEllipse(QPointF(point2.x, point2.z) * scale, INPUT_DOT_RADIUS, INPUT_DOT_RADIUS);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only draw text if data is present.
|
p.setBrush(Qt::NoBrush);
|
||||||
if (!gyro_state.has_value())
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Angle of red dot from starting position.
|
// Red dot upright target.
|
||||||
const auto angle = std::acos(point.Normalized().Dot({0, 0, -1}));
|
p.setPen(QPen(GetAdjustedInputColor(), INPUT_DOT_RADIUS / 2));
|
||||||
|
p.drawEllipse(QPointF{0, -SPHERE_INDICATOR_DIST} * scale, INPUT_DOT_RADIUS, INPUT_DOT_RADIUS);
|
||||||
|
|
||||||
// Angle text:
|
// Blue dot target.
|
||||||
p.setPen(GetTextColor());
|
p.setPen(QPen(Qt::blue, INPUT_DOT_RADIUS / 2));
|
||||||
p.drawText(QRectF(-2, 0, scale, scale), Qt::AlignBottom | Qt::AlignRight,
|
p.drawEllipse(QPointF{}, INPUT_DOT_RADIUS, INPUT_DOT_RADIUS);
|
||||||
// i18n: "°" is the symbol for degrees (angular measurement).
|
|
||||||
QString::fromStdString(fmt::format("{:.2f} °", angle / MathUtil::TAU * 360)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MappingIndicator::DrawCalibration(QPainter& p, Common::DVec2 point)
|
void MappingIndicator::DrawCalibration(QPainter& p, Common::DVec2 point)
|
||||||
|
@ -101,6 +101,7 @@ public:
|
|||||||
private:
|
private:
|
||||||
ControllerEmu::IMUGyroscope& m_gyro_group;
|
ControllerEmu::IMUGyroscope& m_gyro_group;
|
||||||
Common::Matrix33 m_state;
|
Common::Matrix33 m_state;
|
||||||
|
Common::Vec3 m_previous_velocity = {};
|
||||||
u32 m_stable_steps = 0;
|
u32 m_stable_steps = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -334,18 +334,20 @@ void WiiTASInputWindow::GetValues(DataReportBuilder& rpt, int ext,
|
|||||||
DataReportBuilder::CoreData core;
|
DataReportBuilder::CoreData core;
|
||||||
rpt.GetCoreData(&core);
|
rpt.GetCoreData(&core);
|
||||||
|
|
||||||
|
using EmuWiimote = WiimoteEmu::Wiimote;
|
||||||
|
|
||||||
u16& buttons = core.hex;
|
u16& buttons = core.hex;
|
||||||
GetButton<u16>(m_a_button, buttons, WiimoteEmu::Wiimote::BUTTON_A);
|
GetButton<u16>(m_a_button, buttons, EmuWiimote::BUTTON_A);
|
||||||
GetButton<u16>(m_b_button, buttons, WiimoteEmu::Wiimote::BUTTON_B);
|
GetButton<u16>(m_b_button, buttons, EmuWiimote::BUTTON_B);
|
||||||
GetButton<u16>(m_1_button, buttons, WiimoteEmu::Wiimote::BUTTON_ONE);
|
GetButton<u16>(m_1_button, buttons, EmuWiimote::BUTTON_ONE);
|
||||||
GetButton<u16>(m_2_button, buttons, WiimoteEmu::Wiimote::BUTTON_TWO);
|
GetButton<u16>(m_2_button, buttons, EmuWiimote::BUTTON_TWO);
|
||||||
GetButton<u16>(m_plus_button, buttons, WiimoteEmu::Wiimote::BUTTON_PLUS);
|
GetButton<u16>(m_plus_button, buttons, EmuWiimote::BUTTON_PLUS);
|
||||||
GetButton<u16>(m_minus_button, buttons, WiimoteEmu::Wiimote::BUTTON_MINUS);
|
GetButton<u16>(m_minus_button, buttons, EmuWiimote::BUTTON_MINUS);
|
||||||
GetButton<u16>(m_home_button, buttons, WiimoteEmu::Wiimote::BUTTON_HOME);
|
GetButton<u16>(m_home_button, buttons, EmuWiimote::BUTTON_HOME);
|
||||||
GetButton<u16>(m_left_button, buttons, WiimoteEmu::Wiimote::PAD_LEFT);
|
GetButton<u16>(m_left_button, buttons, EmuWiimote::PAD_LEFT);
|
||||||
GetButton<u16>(m_up_button, buttons, WiimoteEmu::Wiimote::PAD_UP);
|
GetButton<u16>(m_up_button, buttons, EmuWiimote::PAD_UP);
|
||||||
GetButton<u16>(m_down_button, buttons, WiimoteEmu::Wiimote::PAD_DOWN);
|
GetButton<u16>(m_down_button, buttons, EmuWiimote::PAD_DOWN);
|
||||||
GetButton<u16>(m_right_button, buttons, WiimoteEmu::Wiimote::PAD_RIGHT);
|
GetButton<u16>(m_right_button, buttons, EmuWiimote::PAD_RIGHT);
|
||||||
|
|
||||||
rpt.SetCoreData(core);
|
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.
|
// FYI: Interleaved reports may behave funky as not all data is always available.
|
||||||
|
|
||||||
DataReportBuilder::AccelData accel;
|
AccelData accel;
|
||||||
rpt.GetAccelData(&accel);
|
rpt.GetAccelData(&accel);
|
||||||
|
|
||||||
GetSpinBoxU16(m_remote_orientation_x_value, accel.x);
|
GetSpinBoxU16(m_remote_orientation_x_value, accel.value.x);
|
||||||
GetSpinBoxU16(m_remote_orientation_y_value, accel.y);
|
GetSpinBoxU16(m_remote_orientation_y_value, accel.value.y);
|
||||||
GetSpinBoxU16(m_remote_orientation_z_value, accel.z);
|
GetSpinBoxU16(m_remote_orientation_z_value, accel.value.z);
|
||||||
|
|
||||||
rpt.SetAccelData(accel);
|
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_x_value, nunchuk.jx);
|
||||||
GetSpinBoxU8(m_nunchuk_stick_y_value, nunchuk.jy);
|
GetSpinBoxU8(m_nunchuk_stick_y_value, nunchuk.jy);
|
||||||
|
|
||||||
u16 accel_x = nunchuk.ax << 2 & (nunchuk.bt.acc_x_lsb & 0b11);
|
auto accel = nunchuk.GetAccel().value;
|
||||||
u16 accel_y = nunchuk.ay << 2 & (nunchuk.bt.acc_y_lsb & 0b11);
|
GetSpinBoxU16(m_nunchuk_orientation_x_value, accel.x);
|
||||||
u16 accel_z = nunchuk.az << 2 & (nunchuk.bt.acc_z_lsb & 0b11);
|
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);
|
u8 bt = nunchuk.GetButtons();
|
||||||
GetSpinBoxU16(m_nunchuk_orientation_y_value, accel_y);
|
GetButton<u8>(m_c_button, bt, WiimoteEmu::Nunchuk::BUTTON_C);
|
||||||
GetSpinBoxU16(m_nunchuk_orientation_z_value, accel_z);
|
GetButton<u8>(m_z_button, bt, WiimoteEmu::Nunchuk::BUTTON_Z);
|
||||||
|
nunchuk.SetButtons(bt);
|
||||||
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;
|
|
||||||
|
|
||||||
key.Encrypt(reinterpret_cast<u8*>(&nunchuk), 0, sizeof(nunchuk));
|
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);
|
auto& cc = *reinterpret_cast<WiimoteEmu::Classic::DataFormat*>(ext_data);
|
||||||
key.Decrypt(reinterpret_cast<u8*>(&cc), 0, sizeof(cc));
|
key.Decrypt(reinterpret_cast<u8*>(&cc), 0, sizeof(cc));
|
||||||
|
|
||||||
cc.bt.hex ^= 0xFFFF;
|
u16 bt = cc.GetButtons();
|
||||||
GetButton<u16>(m_classic_a_button, cc.bt.hex, WiimoteEmu::Classic::BUTTON_A);
|
GetButton<u16>(m_classic_a_button, bt, WiimoteEmu::Classic::BUTTON_A);
|
||||||
GetButton<u16>(m_classic_b_button, cc.bt.hex, WiimoteEmu::Classic::BUTTON_B);
|
GetButton<u16>(m_classic_b_button, bt, WiimoteEmu::Classic::BUTTON_B);
|
||||||
GetButton<u16>(m_classic_x_button, cc.bt.hex, WiimoteEmu::Classic::BUTTON_X);
|
GetButton<u16>(m_classic_x_button, bt, WiimoteEmu::Classic::BUTTON_X);
|
||||||
GetButton<u16>(m_classic_y_button, cc.bt.hex, WiimoteEmu::Classic::BUTTON_Y);
|
GetButton<u16>(m_classic_y_button, bt, WiimoteEmu::Classic::BUTTON_Y);
|
||||||
GetButton<u16>(m_classic_plus_button, cc.bt.hex, WiimoteEmu::Classic::BUTTON_PLUS);
|
GetButton<u16>(m_classic_plus_button, bt, WiimoteEmu::Classic::BUTTON_PLUS);
|
||||||
GetButton<u16>(m_classic_minus_button, cc.bt.hex, WiimoteEmu::Classic::BUTTON_MINUS);
|
GetButton<u16>(m_classic_minus_button, bt, WiimoteEmu::Classic::BUTTON_MINUS);
|
||||||
GetButton<u16>(m_classic_l_button, cc.bt.hex, WiimoteEmu::Classic::TRIGGER_L);
|
GetButton<u16>(m_classic_l_button, bt, WiimoteEmu::Classic::TRIGGER_L);
|
||||||
GetButton<u16>(m_classic_r_button, cc.bt.hex, WiimoteEmu::Classic::TRIGGER_R);
|
GetButton<u16>(m_classic_r_button, bt, WiimoteEmu::Classic::TRIGGER_R);
|
||||||
GetButton<u16>(m_classic_zl_button, cc.bt.hex, WiimoteEmu::Classic::BUTTON_ZL);
|
GetButton<u16>(m_classic_zl_button, bt, WiimoteEmu::Classic::BUTTON_ZL);
|
||||||
GetButton<u16>(m_classic_zr_button, cc.bt.hex, WiimoteEmu::Classic::BUTTON_ZR);
|
GetButton<u16>(m_classic_zr_button, bt, WiimoteEmu::Classic::BUTTON_ZR);
|
||||||
GetButton<u16>(m_classic_home_button, cc.bt.hex, WiimoteEmu::Classic::BUTTON_HOME);
|
GetButton<u16>(m_classic_home_button, bt, WiimoteEmu::Classic::BUTTON_HOME);
|
||||||
GetButton<u16>(m_classic_left_button, cc.bt.hex, WiimoteEmu::Classic::PAD_LEFT);
|
GetButton<u16>(m_classic_left_button, bt, WiimoteEmu::Classic::PAD_LEFT);
|
||||||
GetButton<u16>(m_classic_up_button, cc.bt.hex, WiimoteEmu::Classic::PAD_UP);
|
GetButton<u16>(m_classic_up_button, bt, WiimoteEmu::Classic::PAD_UP);
|
||||||
GetButton<u16>(m_classic_down_button, cc.bt.hex, WiimoteEmu::Classic::PAD_DOWN);
|
GetButton<u16>(m_classic_down_button, bt, WiimoteEmu::Classic::PAD_DOWN);
|
||||||
GetButton<u16>(m_classic_right_button, cc.bt.hex, WiimoteEmu::Classic::PAD_RIGHT);
|
GetButton<u16>(m_classic_right_button, bt, WiimoteEmu::Classic::PAD_RIGHT);
|
||||||
cc.bt.hex ^= 0xFFFF;
|
cc.SetButtons(bt);
|
||||||
|
|
||||||
u8 rx = (cc.rx1 & 0b1) & ((cc.rx2 & 0b11) << 1) & ((cc.rx3 & 0b11) << 3);
|
auto right_stick = cc.GetRightStick().value;
|
||||||
GetSpinBoxU8(m_classic_right_stick_x_value, rx);
|
GetSpinBoxU8(m_classic_right_stick_x_value, right_stick.x);
|
||||||
cc.rx1 = rx & 0b1;
|
GetSpinBoxU8(m_classic_right_stick_y_value, right_stick.y);
|
||||||
cc.rx2 = (rx >> 1) & 0b11;
|
cc.SetRightStick(right_stick);
|
||||||
cc.rx3 = (rx >> 3) & 0b11;
|
|
||||||
|
|
||||||
u8 ry = cc.ry;
|
auto left_stick = cc.GetLeftStick().value;
|
||||||
GetSpinBoxU8(m_classic_right_stick_y_value, ry);
|
GetSpinBoxU8(m_classic_left_stick_x_value, left_stick.x);
|
||||||
cc.ry = ry;
|
GetSpinBoxU8(m_classic_left_stick_y_value, left_stick.y);
|
||||||
|
cc.SetLeftStick(left_stick);
|
||||||
|
|
||||||
u8 lx = cc.lx;
|
u8 rt = cc.GetRightTrigger().value;
|
||||||
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;
|
|
||||||
GetSpinBoxU8(m_right_trigger_value, rt);
|
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);
|
GetSpinBoxU8(m_left_trigger_value, lt);
|
||||||
cc.lt1 = lt & 0b111;
|
cc.SetLeftTrigger(lt);
|
||||||
cc.lt2 = (lt >> 3) & 0b11;
|
|
||||||
|
|
||||||
key.Encrypt(reinterpret_cast<u8*>(&cc), 0, sizeof(cc));
|
key.Encrypt(reinterpret_cast<u8*>(&cc), 0, sizeof(cc));
|
||||||
}
|
}
|
||||||
|
@ -50,6 +50,8 @@ add_library(inputcommon
|
|||||||
ControllerInterface/ControllerInterface.h
|
ControllerInterface/ControllerInterface.h
|
||||||
ControllerInterface/Device.cpp
|
ControllerInterface/Device.cpp
|
||||||
ControllerInterface/Device.h
|
ControllerInterface/Device.h
|
||||||
|
ControllerInterface/Wiimote/Wiimote.cpp
|
||||||
|
ControllerInterface/Wiimote/Wiimote.h
|
||||||
ControlReference/ControlReference.cpp
|
ControlReference/ControlReference.cpp
|
||||||
ControlReference/ControlReference.h
|
ControlReference/ControlReference.h
|
||||||
ControlReference/ExpressionParser.cpp
|
ControlReference/ExpressionParser.cpp
|
||||||
|
@ -50,7 +50,7 @@ Cursor::Cursor(std::string name, std::string ui_name)
|
|||||||
_trans("°"),
|
_trans("°"),
|
||||||
// i18n: Refers to emulated wii remote movements.
|
// i18n: Refers to emulated wii remote movements.
|
||||||
_trans("Total rotation about the yaw axis.")},
|
_trans("Total rotation about the yaw axis.")},
|
||||||
15, 0, 180);
|
15, 0, 360);
|
||||||
|
|
||||||
AddSetting(&m_pitch_setting,
|
AddSetting(&m_pitch_setting,
|
||||||
// i18n: Refers to an amount of rotational movement about the "pitch" axis.
|
// 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("°"),
|
_trans("°"),
|
||||||
// i18n: Refers to emulated wii remote movements.
|
// i18n: Refers to emulated wii remote movements.
|
||||||
_trans("Total rotation about the pitch axis.")},
|
_trans("Total rotation about the pitch axis.")},
|
||||||
15, 0, 180);
|
15, 0, 360);
|
||||||
|
|
||||||
AddSetting(&m_relative_setting, {_trans("Relative Input")}, false);
|
AddSetting(&m_relative_setting, {_trans("Relative Input")}, false);
|
||||||
AddSetting(&m_autohide_setting, {_trans("Auto-Hide")}, false);
|
AddSetting(&m_autohide_setting, {_trans("Auto-Hide")}, false);
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
#include "Common/Common.h"
|
#include "Common/Common.h"
|
||||||
|
#include "Common/MathUtil.h"
|
||||||
|
|
||||||
#include "InputCommon/ControlReference/ControlReference.h"
|
#include "InputCommon/ControlReference/ControlReference.h"
|
||||||
#include "InputCommon/ControllerEmu/Control/Control.h"
|
#include "InputCommon/ControllerEmu/Control/Control.h"
|
||||||
@ -14,6 +15,15 @@
|
|||||||
|
|
||||||
namespace ControllerEmu
|
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)
|
IMUGyroscope::IMUGyroscope(std::string name, std::string ui_name)
|
||||||
: ControlGroup(std::move(name), std::move(ui_name), GroupType::IMUGyroscope)
|
: 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("Roll Right"));
|
||||||
AddInput(Translate, _trans("Yaw Left"));
|
AddInput(Translate, _trans("Yaw Left"));
|
||||||
AddInput(Translate, _trans("Yaw Right"));
|
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
|
std::optional<IMUGyroscope::StateData> IMUGyroscope::GetState() const
|
||||||
{
|
{
|
||||||
if (controls[0]->control_ref->BoundCount() == 0)
|
if (controls[0]->control_ref->BoundCount() == 0)
|
||||||
|
{
|
||||||
|
// Set calibration to zero.
|
||||||
|
m_calibration = {};
|
||||||
|
RestartCalibration();
|
||||||
return std::nullopt;
|
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;
|
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
|
} // namespace ControllerEmu
|
||||||
|
@ -4,11 +4,14 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
#include "Common/MathUtil.h"
|
||||||
#include "Common/Matrix.h"
|
#include "Common/Matrix.h"
|
||||||
#include "InputCommon/ControllerEmu/ControlGroup/ControlGroup.h"
|
#include "InputCommon/ControllerEmu/ControlGroup/ControlGroup.h"
|
||||||
|
#include "InputCommon/ControllerEmu/Setting/NumericSetting.h"
|
||||||
|
|
||||||
namespace ControllerEmu
|
namespace ControllerEmu
|
||||||
{
|
{
|
||||||
@ -19,6 +22,25 @@ public:
|
|||||||
|
|
||||||
IMUGyroscope(std::string name, std::string ui_name);
|
IMUGyroscope(std::string name, std::string ui_name);
|
||||||
|
|
||||||
|
StateData GetRawState() const;
|
||||||
std::optional<StateData> GetState() 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
|
} // namespace ControllerEmu
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "Common/BitUtils.h"
|
||||||
#include "Common/Common.h"
|
#include "Common/Common.h"
|
||||||
#include "Common/IniFile.h"
|
#include "Common/IniFile.h"
|
||||||
#include "InputCommon/ControlReference/ExpressionParser.h"
|
#include "InputCommon/ControlReference/ExpressionParser.h"
|
||||||
@ -27,6 +28,106 @@ namespace ControllerEmu
|
|||||||
{
|
{
|
||||||
class ControlGroup;
|
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
|
class EmulatedController
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
#include "Common/Logging/Log.h"
|
#include "Common/Logging/Log.h"
|
||||||
|
#include "Core/HW/WiimoteReal/WiimoteReal.h"
|
||||||
|
|
||||||
#ifdef CIFACE_USE_WIN32
|
#ifdef CIFACE_USE_WIN32
|
||||||
#include "InputCommon/ControllerInterface/Win32/Win32.h"
|
#include "InputCommon/ControllerInterface/Win32/Win32.h"
|
||||||
@ -93,7 +94,7 @@ void ControllerInterface::RefreshDevices()
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lk(m_devices_mutex);
|
std::lock_guard lk(m_devices_mutex);
|
||||||
m_devices.clear();
|
m_devices.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,6 +133,8 @@ void ControllerInterface::RefreshDevices()
|
|||||||
ciface::DualShockUDPClient::PopulateDevices();
|
ciface::DualShockUDPClient::PopulateDevices();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
WiimoteReal::ProcessWiimotePool();
|
||||||
|
|
||||||
m_is_populating_devices = false;
|
m_is_populating_devices = false;
|
||||||
InvokeDevicesChangedCallbacks();
|
InvokeDevicesChangedCallbacks();
|
||||||
}
|
}
|
||||||
@ -146,7 +149,7 @@ void ControllerInterface::Shutdown()
|
|||||||
m_is_init = false;
|
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)
|
for (const auto& d : m_devices)
|
||||||
{
|
{
|
||||||
@ -193,7 +196,7 @@ void ControllerInterface::AddDevice(std::shared_ptr<ciface::Core::Device> device
|
|||||||
return;
|
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) {
|
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) {
|
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)
|
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) {
|
auto it = std::remove_if(m_devices.begin(), m_devices.end(), [&callback](const auto& dev) {
|
||||||
if (callback(dev.get()))
|
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)
|
// Don't block the UI or CPU thread (to avoid a short but noticeable frame drop)
|
||||||
if (m_devices_mutex.try_lock())
|
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)
|
for (const auto& d : m_devices)
|
||||||
d->UpdateInput();
|
d->UpdateInput();
|
||||||
}
|
}
|
||||||
|
@ -180,7 +180,7 @@ bool DeviceQualifier::operator!=(const DeviceQualifier& devq) const
|
|||||||
|
|
||||||
std::shared_ptr<Device> DeviceContainer::FindDevice(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)
|
for (const auto& d : m_devices)
|
||||||
{
|
{
|
||||||
if (devq == d.get())
|
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::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;
|
std::vector<std::string> device_strings;
|
||||||
DeviceQualifier device_qualifier;
|
DeviceQualifier device_qualifier;
|
||||||
@ -208,7 +208,7 @@ std::vector<std::string> DeviceContainer::GetAllDeviceStrings() const
|
|||||||
|
|
||||||
std::string DeviceContainer::GetDefaultDeviceString() 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())
|
if (m_devices.empty())
|
||||||
return "";
|
return "";
|
||||||
|
|
||||||
@ -226,7 +226,7 @@ Device::Input* DeviceContainer::FindInput(std::string_view name, const Device* d
|
|||||||
return inp;
|
return inp;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::lock_guard<std::mutex> lk(m_devices_mutex);
|
std::lock_guard lk(m_devices_mutex);
|
||||||
for (const auto& d : m_devices)
|
for (const auto& d : m_devices)
|
||||||
{
|
{
|
||||||
Device::Input* const i = d->FindInput(name);
|
Device::Input* const i = d->FindInput(name);
|
||||||
|
@ -198,7 +198,7 @@ public:
|
|||||||
DetectInput(u32 wait_ms, const std::vector<std::string>& device_strings) const;
|
DetectInput(u32 wait_ms, const std::vector<std::string>& device_strings) const;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
mutable std::mutex m_devices_mutex;
|
mutable std::recursive_mutex m_devices_mutex;
|
||||||
std::vector<std::shared_ptr<Device>> m_devices;
|
std::vector<std::shared_ptr<Device>> m_devices;
|
||||||
};
|
};
|
||||||
} // namespace Core
|
} // namespace Core
|
||||||
|
1592
Source/Core/InputCommon/ControllerInterface/Wiimote/Wiimote.cpp
Normal file
1592
Source/Core/InputCommon/ControllerInterface/Wiimote/Wiimote.cpp
Normal file
File diff suppressed because it is too large
Load Diff
270
Source/Core/InputCommon/ControllerInterface/Wiimote/Wiimote.h
Normal file
270
Source/Core/InputCommon/ControllerInterface/Wiimote/Wiimote.h
Normal file
@ -0,0 +1,270 @@
|
|||||||
|
// Copyright 2020 Dolphin Emulator Project
|
||||||
|
// Licensed under GPLv2+
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <chrono>
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "Core/HW/WiimoteCommon/DataReport.h"
|
||||||
|
#include "Core/HW/WiimoteCommon/WiimoteReport.h"
|
||||||
|
#include "Core/HW/WiimoteEmu/Camera.h"
|
||||||
|
#include "Core/HW/WiimoteEmu/Extension/Classic.h"
|
||||||
|
#include "Core/HW/WiimoteEmu/Extension/Nunchuk.h"
|
||||||
|
#include "Core/HW/WiimoteEmu/MotionPlus.h"
|
||||||
|
#include "Core/HW/WiimoteReal/WiimoteReal.h"
|
||||||
|
#include "InputCommon/ControllerInterface/Device.h"
|
||||||
|
|
||||||
|
namespace ciface::Wiimote
|
||||||
|
{
|
||||||
|
using namespace WiimoteCommon;
|
||||||
|
|
||||||
|
void AddDevice(std::unique_ptr<WiimoteReal::Wiimote>);
|
||||||
|
void ReleaseDevices(std::optional<u32> count = std::nullopt);
|
||||||
|
|
||||||
|
class Device final : public Core::Device
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Device(std::unique_ptr<WiimoteReal::Wiimote> wiimote);
|
||||||
|
~Device();
|
||||||
|
|
||||||
|
std::string GetName() const override;
|
||||||
|
std::string GetSource() const override;
|
||||||
|
|
||||||
|
void UpdateInput() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
using Clock = std::chrono::steady_clock;
|
||||||
|
|
||||||
|
enum class ExtensionID
|
||||||
|
{
|
||||||
|
Nunchuk,
|
||||||
|
Classic,
|
||||||
|
Unsupported,
|
||||||
|
};
|
||||||
|
|
||||||
|
class MotionPlusState
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void SetCalibrationData(const WiimoteEmu::MotionPlus::CalibrationData&);
|
||||||
|
void ProcessData(const WiimoteEmu::MotionPlus::DataFormat&);
|
||||||
|
|
||||||
|
using PassthroughMode = WiimoteEmu::MotionPlus::PassthroughMode;
|
||||||
|
|
||||||
|
// State is unknown by default.
|
||||||
|
std::optional<PassthroughMode> current_mode;
|
||||||
|
|
||||||
|
// The last known state of the passthrough port flag.
|
||||||
|
// Used to detect passthrough extension port events.
|
||||||
|
std::optional<bool> passthrough_port;
|
||||||
|
|
||||||
|
Common::Vec3 gyro_data = {};
|
||||||
|
|
||||||
|
std::optional<WiimoteEmu::MotionPlus::CalibrationBlocks> calibration;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct NunchukState
|
||||||
|
{
|
||||||
|
using CalibrationData = WiimoteEmu::Nunchuk::CalibrationData;
|
||||||
|
|
||||||
|
void SetCalibrationData(const CalibrationData&);
|
||||||
|
void ProcessData(const WiimoteEmu::Nunchuk::DataFormat&);
|
||||||
|
|
||||||
|
Common::Vec2 stick = {};
|
||||||
|
Common::Vec3 accel = {};
|
||||||
|
|
||||||
|
u8 buttons = 0;
|
||||||
|
|
||||||
|
struct Calibration
|
||||||
|
{
|
||||||
|
CalibrationData::AccelCalibration accel;
|
||||||
|
CalibrationData::StickCalibration stick;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::optional<Calibration> calibration;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ClassicState
|
||||||
|
{
|
||||||
|
using CalibrationData = WiimoteEmu::Classic::CalibrationData;
|
||||||
|
|
||||||
|
void SetCalibrationData(const CalibrationData&);
|
||||||
|
void ProcessData(const WiimoteEmu::Classic::DataFormat&);
|
||||||
|
|
||||||
|
std::array<Common::Vec2, 2> sticks = {};
|
||||||
|
std::array<float, 2> triggers = {};
|
||||||
|
|
||||||
|
u16 buttons = 0;
|
||||||
|
|
||||||
|
struct Calibration
|
||||||
|
{
|
||||||
|
CalibrationData::StickCalibration left_stick;
|
||||||
|
CalibrationData::StickCalibration right_stick;
|
||||||
|
|
||||||
|
CalibrationData::TriggerCalibration left_trigger;
|
||||||
|
CalibrationData::TriggerCalibration right_trigger;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::optional<Calibration> calibration;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct IRState
|
||||||
|
{
|
||||||
|
static u32 GetDesiredIRSensitivity();
|
||||||
|
|
||||||
|
void ProcessData(const std::array<WiimoteEmu::IRBasic, 2>&);
|
||||||
|
bool IsFullyConfigured() const;
|
||||||
|
|
||||||
|
u32 current_sensitivity = u32(-1);
|
||||||
|
bool enabled = false;
|
||||||
|
bool mode_set = false;
|
||||||
|
|
||||||
|
// Average of visible IR "objects".
|
||||||
|
Common::Vec2 center_position = {};
|
||||||
|
|
||||||
|
bool is_hidden = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ReportHandler
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
enum class HandlerResult
|
||||||
|
{
|
||||||
|
Handled,
|
||||||
|
NotHandled,
|
||||||
|
};
|
||||||
|
|
||||||
|
ReportHandler(Clock::time_point expired_time);
|
||||||
|
|
||||||
|
template <typename R, typename T>
|
||||||
|
void AddHandler(std::function<R(const T&)>);
|
||||||
|
|
||||||
|
HandlerResult TryToHandleReport(const WiimoteReal::Report& report);
|
||||||
|
|
||||||
|
bool IsExpired() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
const Clock::time_point m_expired_time;
|
||||||
|
std::vector<std::function<HandlerResult(const WiimoteReal::Report& report)>> m_callbacks;
|
||||||
|
};
|
||||||
|
|
||||||
|
using AckReportHandler = std::function<ReportHandler::HandlerResult(const InputReportAck& reply)>;
|
||||||
|
|
||||||
|
static AckReportHandler MakeAckHandler(OutputReportID report_id,
|
||||||
|
std::function<void(WiimoteCommon::ErrorCode)> callback);
|
||||||
|
|
||||||
|
// TODO: Make parameter const. (need to modify DataReportManipulator)
|
||||||
|
void ProcessInputReport(WiimoteReal::Report& report);
|
||||||
|
void ProcessMotionPlusExtensionData(const u8* data, u32 size);
|
||||||
|
void ProcessNormalExtensionData(const u8* data, u32 size);
|
||||||
|
void ProcessExtensionEvent(bool connected);
|
||||||
|
void ProcessExtensionID(u8 id_0, u8 id_4, u8 id_5);
|
||||||
|
void ProcessStatusReport(const InputReportStatus&);
|
||||||
|
|
||||||
|
void RunTasks();
|
||||||
|
|
||||||
|
bool IsPerformingTask() const;
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void QueueReport(T&& report, std::function<void(ErrorCode)> ack_callback = {});
|
||||||
|
|
||||||
|
template <typename... T>
|
||||||
|
void AddReportHandler(T&&... callbacks);
|
||||||
|
|
||||||
|
using ReadResponse = std::optional<std::vector<u8>>;
|
||||||
|
|
||||||
|
void ReadData(AddressSpace space, u8 slave, u16 address, u16 size,
|
||||||
|
std::function<void(ReadResponse)> callback);
|
||||||
|
|
||||||
|
void AddReadDataReplyHandler(AddressSpace space, u8 slave, u16 address, u16 size,
|
||||||
|
std::vector<u8> starting_data,
|
||||||
|
std::function<void(ReadResponse)> callback);
|
||||||
|
|
||||||
|
template <typename T = std::initializer_list<u8>, typename C>
|
||||||
|
void WriteData(AddressSpace space, u8 slave, u16 address, T&& data, C&& callback);
|
||||||
|
|
||||||
|
void ReadActiveExtensionID();
|
||||||
|
void SetIRSensitivity(u32 level);
|
||||||
|
void ConfigureSpeaker();
|
||||||
|
void ConfigureIRCamera();
|
||||||
|
|
||||||
|
u8 GetDesiredLEDValue() const;
|
||||||
|
|
||||||
|
void TriggerMotionPlusModeChange();
|
||||||
|
void TriggerMotionPlusCalibration();
|
||||||
|
|
||||||
|
bool IsMotionPlusStateKnown() const;
|
||||||
|
bool IsMotionPlusActive() const;
|
||||||
|
bool IsMotionPlusInDesiredMode() const;
|
||||||
|
|
||||||
|
bool IsWaitingForMotionPlus() const;
|
||||||
|
void WaitForMotionPlus();
|
||||||
|
void HandleMotionPlusNonResponse();
|
||||||
|
|
||||||
|
void UpdateRumble();
|
||||||
|
void UpdateOrientation();
|
||||||
|
void UpdateExtensionNumberInput();
|
||||||
|
|
||||||
|
std::unique_ptr<WiimoteReal::Wiimote> m_wiimote;
|
||||||
|
|
||||||
|
// Buttons.
|
||||||
|
DataReportManipulator::CoreData m_core_data = {};
|
||||||
|
|
||||||
|
// Accelerometer.
|
||||||
|
Common::Vec3 m_accel_data = {};
|
||||||
|
std::optional<AccelCalibrationData::Calibration> m_accel_calibration;
|
||||||
|
|
||||||
|
// Pitch, Roll, Yaw inputs.
|
||||||
|
Common::Vec3 m_rotation_inputs = {};
|
||||||
|
|
||||||
|
MotionPlusState m_mplus_state = {};
|
||||||
|
NunchukState m_nunchuk_state = {};
|
||||||
|
ClassicState m_classic_state = {};
|
||||||
|
IRState m_ir_state = {};
|
||||||
|
|
||||||
|
// Used to poll for M+ periodically and wait for it to reset.
|
||||||
|
Clock::time_point m_mplus_wait_time = Clock::now();
|
||||||
|
|
||||||
|
// The desired mode is set based on the attached normal extension.
|
||||||
|
std::optional<MotionPlusState::PassthroughMode> m_mplus_desired_mode;
|
||||||
|
|
||||||
|
// Status report is requested every so often to update the battery level.
|
||||||
|
Clock::time_point m_status_outdated_time = Clock::now();
|
||||||
|
u8 m_battery = 0;
|
||||||
|
u8 m_leds = 0;
|
||||||
|
|
||||||
|
bool m_speaker_configured = false;
|
||||||
|
|
||||||
|
// The last known state of the extension port status flag.
|
||||||
|
// Used to detect extension port events.
|
||||||
|
std::optional<bool> m_extension_port;
|
||||||
|
|
||||||
|
// Note this refers to the passthrough extension when M+ is active.
|
||||||
|
std::optional<ExtensionID> m_extension_id;
|
||||||
|
|
||||||
|
// Rumble state must be saved to set the proper flag in every output report.
|
||||||
|
bool m_rumble = false;
|
||||||
|
|
||||||
|
// For pulse of rumble motor to simulate multiple levels.
|
||||||
|
ControlState m_rumble_level = 0;
|
||||||
|
Clock::time_point m_last_rumble_change = Clock::now();
|
||||||
|
|
||||||
|
// Assume mode is disabled so one gets set.
|
||||||
|
InputReportID m_reporting_mode = InputReportID::ReportDisabled;
|
||||||
|
|
||||||
|
// Used only to provide a value for a specialty "input". (for attached extension passthrough)
|
||||||
|
WiimoteEmu::ExtensionNumber m_extension_number_input = WiimoteEmu::ExtensionNumber::NONE;
|
||||||
|
bool m_mplus_attached_input = false;
|
||||||
|
|
||||||
|
// Holds callbacks for output report replies.
|
||||||
|
std::list<ReportHandler> m_report_handlers;
|
||||||
|
|
||||||
|
// World rotation. (used to rotate IR data and provide pitch, roll, yaw inputs)
|
||||||
|
Common::Matrix33 m_orientation = Common::Matrix33::Identity();
|
||||||
|
Clock::time_point m_last_report_time = Clock::now();
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace ciface::Wiimote
|
@ -75,6 +75,7 @@
|
|||||||
<ClCompile Include="ControlReference\ExpressionParser.cpp" />
|
<ClCompile Include="ControlReference\ExpressionParser.cpp" />
|
||||||
<ClCompile Include="ControllerInterface\ForceFeedback\ForceFeedbackDevice.cpp" />
|
<ClCompile Include="ControllerInterface\ForceFeedback\ForceFeedbackDevice.cpp" />
|
||||||
<ClCompile Include="ControllerInterface\Win32\Win32.cpp" />
|
<ClCompile Include="ControllerInterface\Win32\Win32.cpp" />
|
||||||
|
<ClCompile Include="ControllerInterface\Wiimote\Wiimote.cpp" />
|
||||||
<ClCompile Include="ControllerInterface\XInput\XInput.cpp" />
|
<ClCompile Include="ControllerInterface\XInput\XInput.cpp" />
|
||||||
<ClCompile Include="ControlReference\FunctionExpression.cpp" />
|
<ClCompile Include="ControlReference\FunctionExpression.cpp" />
|
||||||
<ClCompile Include="GCAdapter.cpp">
|
<ClCompile Include="GCAdapter.cpp">
|
||||||
@ -122,6 +123,7 @@
|
|||||||
<ClInclude Include="ControlReference\ExpressionParser.h" />
|
<ClInclude Include="ControlReference\ExpressionParser.h" />
|
||||||
<ClInclude Include="ControllerInterface\ForceFeedback\ForceFeedbackDevice.h" />
|
<ClInclude Include="ControllerInterface\ForceFeedback\ForceFeedbackDevice.h" />
|
||||||
<ClInclude Include="ControllerInterface\Win32\Win32.h" />
|
<ClInclude Include="ControllerInterface\Win32\Win32.h" />
|
||||||
|
<ClInclude Include="ControllerInterface\Wiimote\Wiimote.h" />
|
||||||
<ClInclude Include="ControllerInterface\XInput\XInput.h" />
|
<ClInclude Include="ControllerInterface\XInput\XInput.h" />
|
||||||
<ClInclude Include="GCAdapter.h" />
|
<ClInclude Include="GCAdapter.h" />
|
||||||
<ClInclude Include="GCPadStatus.h" />
|
<ClInclude Include="GCPadStatus.h" />
|
||||||
@ -139,4 +141,4 @@
|
|||||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||||
<ImportGroup Label="ExtensionTargets">
|
<ImportGroup Label="ExtensionTargets">
|
||||||
</ImportGroup>
|
</ImportGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -110,6 +110,9 @@
|
|||||||
<ClCompile Include="ControllerInterface\Win32\Win32.cpp">
|
<ClCompile Include="ControllerInterface\Win32\Win32.cpp">
|
||||||
<Filter>ControllerInterface\Win32</Filter>
|
<Filter>ControllerInterface\Win32</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="ControllerInterface\Wiimote\Wiimote.cpp">
|
||||||
|
<Filter>ControllerInterface\Wiimote</Filter>
|
||||||
|
</ClCompile>
|
||||||
<ClCompile Include="ControlReference\ExpressionParser.cpp">
|
<ClCompile Include="ControlReference\ExpressionParser.cpp">
|
||||||
<Filter>ControllerInterface</Filter>
|
<Filter>ControllerInterface</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
@ -218,6 +221,9 @@
|
|||||||
<ClInclude Include="ControllerInterface\Win32\Win32.h">
|
<ClInclude Include="ControllerInterface\Win32\Win32.h">
|
||||||
<Filter>ControllerInterface\Win32</Filter>
|
<Filter>ControllerInterface\Win32</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
<ClInclude Include="ControllerInterface\Wiimote\Wiimote.h">
|
||||||
|
<Filter>ControllerInterface\Wiimote</Filter>
|
||||||
|
</ClInclude>
|
||||||
<ClInclude Include="ControlReference\ExpressionParser.h">
|
<ClInclude Include="ControlReference\ExpressionParser.h">
|
||||||
<Filter>ControllerInterface</Filter>
|
<Filter>ControllerInterface</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
@ -248,4 +254,4 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Text Include="CMakeLists.txt" />
|
<Text Include="CMakeLists.txt" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
Reference in New Issue
Block a user