WiimoteEmu: Allow shake frequency and intensity to be configured. Other minor cleanups.

This commit is contained in:
Jordan Woyak 2019-03-29 14:39:48 -05:00
parent 635fd8c22c
commit c89ddf8cba
24 changed files with 285 additions and 313 deletions

View File

@ -30,7 +30,6 @@ add_library(core
Config/NetplaySettings.cpp
Config/SYSCONFSettings.cpp
Config/UISettings.cpp
Config/WiimoteInputSettings.cpp
ConfigLoaders/BaseConfigLoader.cpp
ConfigLoaders/GameConfigLoader.cpp
ConfigLoaders/IsSettingSaveable.cpp

View File

@ -1,33 +0,0 @@
// Copyright 2018 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include "Core/Config/WiimoteInputSettings.h"
namespace Config
{
// Configuration Information
// WiimoteInput.Settings
const ConfigInfo<double> WIIMOTE_INPUT_SHAKE_INTENSITY_HARD{{System::WiiPad, "Shake", "Hard"}, 5.0};
const ConfigInfo<double> WIIMOTE_INPUT_SHAKE_INTENSITY_MEDIUM{{System::WiiPad, "Shake", "Medium"},
3.0};
const ConfigInfo<double> WIIMOTE_INPUT_SHAKE_INTENSITY_SOFT{{System::WiiPad, "Shake", "Soft"}, 2.0};
// Dynamic settings
const ConfigInfo<int> WIIMOTE_INPUT_SHAKE_DYNAMIC_FRAMES_HELD_HARD{
{System::WiiPad, "Dynamic_Shake", "FramesHeldHard"}, 45};
const ConfigInfo<int> WIIMOTE_INPUT_SHAKE_DYNAMIC_FRAMES_HELD_SOFT{
{System::WiiPad, "Dynamic_Shake", "FramesHeldSoft"}, 15};
const ConfigInfo<int> WIIMOTE_INPUT_SHAKE_DYNAMIC_FRAMES_LENGTH{
{System::WiiPad, "Dynamic_Shake", "FrameCount"}, 30};
// NunchuckInput.Settings
const ConfigInfo<double> NUNCHUK_INPUT_SHAKE_INTENSITY_HARD{
{System::WiiPad, "Nunchuk_Shake", "Hard"}, 5.0};
const ConfigInfo<double> NUNCHUK_INPUT_SHAKE_INTENSITY_MEDIUM{
{System::WiiPad, "Nunchuk_Shake", "Medium"}, 3.0};
const ConfigInfo<double> NUNCHUK_INPUT_SHAKE_INTENSITY_SOFT{
{System::WiiPad, "Nunchuk_Shake", "Soft"}, 2.0};
} // namespace Config

View File

@ -1,31 +0,0 @@
// Copyright 2018 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#pragma once
#include "Common/Config/Config.h"
namespace Config
{
// Configuration Information
// WiimoteInput.Settings
extern const ConfigInfo<double> WIIMOTE_INPUT_SHAKE_INTENSITY_HARD;
extern const ConfigInfo<double> WIIMOTE_INPUT_SHAKE_INTENSITY_MEDIUM;
extern const ConfigInfo<double> WIIMOTE_INPUT_SHAKE_INTENSITY_SOFT;
// Below settings are for dynamic input only (based on how long the user holds a button)
extern const ConfigInfo<int>
WIIMOTE_INPUT_SHAKE_DYNAMIC_FRAMES_HELD_HARD; // How long button held constitutes a hard shake
extern const ConfigInfo<int>
WIIMOTE_INPUT_SHAKE_DYNAMIC_FRAMES_HELD_SOFT; // How long button held constitutes a soft shake
extern const ConfigInfo<int>
WIIMOTE_INPUT_SHAKE_DYNAMIC_FRAMES_LENGTH; // How long to execute a shake
// NunchuckInput.Settings
extern const ConfigInfo<double> NUNCHUK_INPUT_SHAKE_INTENSITY_HARD;
extern const ConfigInfo<double> NUNCHUK_INPUT_SHAKE_INTENSITY_MEDIUM;
extern const ConfigInfo<double> NUNCHUK_INPUT_SHAKE_INTENSITY_SOFT;
} // namespace Config

View File

@ -56,7 +56,6 @@
<ClCompile Include="ConfigLoaders\MovieConfigLoader.cpp" />
<ClCompile Include="ConfigLoaders\NetPlayConfigLoader.cpp" />
<ClCompile Include="ConfigManager.cpp" />
<ClCompile Include="Config\WiimoteInputSettings.cpp" />
<ClCompile Include="Core.cpp" />
<ClCompile Include="CoreTiming.cpp" />
<ClCompile Include="Debugger\Debugger_SymbolMap.cpp" />
@ -327,7 +326,6 @@
<ClInclude Include="ConfigLoaders\NetPlayConfigLoader.h" />
<ClInclude Include="ConfigManager.h" />
<ClInclude Include="Config\UISettings.h" />
<ClInclude Include="Config\WiimoteInputSettings.h" />
<ClInclude Include="Core.h" />
<ClInclude Include="CoreTiming.h" />
<ClInclude Include="Debugger\Debugger_SymbolMap.h" />

View File

@ -874,9 +874,6 @@
<ClCompile Include="Config\UISettings.cpp">
<Filter>Config</Filter>
</ClCompile>
<ClCompile Include="Config\WiimoteInputSettings.cpp">
<Filter>Config</Filter>
</ClCompile>
<ClCompile Include="PowerPC\Jit64\RegCache\FPURegCache.cpp" />
<ClCompile Include="PowerPC\Jit64\RegCache\GPRRegCache.cpp" />
<ClCompile Include="PowerPC\Jit64\RegCache\JitRegCache.cpp" />
@ -1592,9 +1589,6 @@
<ClInclude Include="Config\UISettings.h">
<Filter>Config</Filter>
</ClInclude>
<ClInclude Include="Config\WiimoteInputSettings.h">
<Filter>Config</Filter>
</ClInclude>
<ClInclude Include="HW\WiimoteEmu\I2CBus.h">
<Filter>HW %28Flipper/Hollywood%29\Wiimote\Emu</Filter>
</ClInclude>
@ -1638,4 +1632,4 @@
<ItemGroup>
<Text Include="CMakeLists.txt" />
</ItemGroup>
</Project>
</Project>

View File

@ -8,8 +8,6 @@
#include "Common/MathUtil.h"
#include "Core/Config/SYSCONFSettings.h"
#include "Core/Config/WiimoteInputSettings.h"
#include "Core/HW/Wiimote.h"
#include "Core/HW/WiimoteEmu/WiimoteEmu.h"
#include "InputCommon/ControllerEmu/ControlGroup/Buttons.h"
#include "InputCommon/ControllerEmu/ControlGroup/Cursor.h"
@ -18,11 +16,6 @@
namespace
{
constexpr int SHAKE_FREQ = 6;
// Frame count of one up/down shake
// < 9 no shake detection in "Wario Land: Shake It"
constexpr int SHAKE_STEP_MAX = ::Wiimote::UPDATE_FREQ / SHAKE_FREQ;
// Given a velocity, acceleration, and maximum jerk value,
// calculate change in position after a stop in the shortest possible time.
// Used to smoothly adjust acceleration and come to complete stops at precise positions.
@ -61,81 +54,32 @@ double CalculateStopDistance(double velocity, double max_accel)
namespace WiimoteEmu
{
Common::Vec3 EmulateShake(ControllerEmu::Buttons* const buttons_group, const double intensity,
u8* const shake_step)
void EmulateShake(PositionalState* state, ControllerEmu::Shake* const shake_group,
float time_elapsed)
{
// shake is a bitfield of X,Y,Z shake button states
static const unsigned int btns[] = {0x01, 0x02, 0x04};
unsigned int shake = 0;
buttons_group->GetState(&shake, btns);
Common::Vec3 accel;
for (std::size_t i = 0; i != accel.data.size(); ++i)
auto target_position = shake_group->GetState() * shake_group->GetIntensity() / 2;
for (std::size_t i = 0; i != target_position.data.size(); ++i)
{
if (shake & (1 << i))
if (state->velocity.data[i] * std::copysign(1.f, target_position.data[i]) < 0 ||
state->position.data[i] / target_position.data[i] > 0.5)
{
accel.data[i] = std::sin(MathUtil::TAU * shake_step[i] / SHAKE_STEP_MAX) * intensity *
GRAVITY_ACCELERATION;
shake_step[i] = (shake_step[i] + 1) % SHAKE_STEP_MAX;
}
else
{
shake_step[i] = 0;
target_position.data[i] *= -1;
}
}
return accel;
}
// Time from "top" to "bottom" of one shake.
const auto travel_time = 1 / shake_group->GetFrequency() / 2;
Common::Vec3 EmulateDynamicShake(DynamicData& dynamic_data,
ControllerEmu::Buttons* const buttons_group,
const DynamicConfiguration& config, u8* const shake_step)
{
// shake is a bitfield of X,Y,Z shake button states
static const unsigned int btns[] = {0x01, 0x02, 0x04};
unsigned int shake = 0;
buttons_group->GetState(&shake, btns);
Common::Vec3 accel;
for (std::size_t i = 0; i != accel.data.size(); ++i)
Common::Vec3 jerk;
for (std::size_t i = 0; i != target_position.data.size(); ++i)
{
if ((shake & (1 << i)) && dynamic_data.executing_frames_left[i] == 0)
{
dynamic_data.timing[i]++;
}
else if (dynamic_data.executing_frames_left[i] > 0)
{
accel.data[i] = std::sin(MathUtil::TAU * shake_step[i] / SHAKE_STEP_MAX) *
dynamic_data.intensity[i] * GRAVITY_ACCELERATION;
shake_step[i] = (shake_step[i] + 1) % SHAKE_STEP_MAX;
dynamic_data.executing_frames_left[i]--;
}
else if (shake == 0 && dynamic_data.timing[i] > 0)
{
if (dynamic_data.timing[i] > config.frames_needed_for_high_intensity)
{
dynamic_data.intensity[i] = config.high_intensity;
}
else if (dynamic_data.timing[i] < config.frames_needed_for_low_intensity)
{
dynamic_data.intensity[i] = config.low_intensity;
}
else
{
dynamic_data.intensity[i] = config.med_intensity;
}
dynamic_data.timing[i] = 0;
dynamic_data.executing_frames_left[i] = config.frames_to_execute;
}
else
{
shake_step[i] = 0;
}
const auto half_distance =
std::max(std::abs(target_position.data[i]), std::abs(state->position.data[i]));
jerk.data[i] = half_distance / std::pow(travel_time / 2, 3);
}
return accel;
ApproachPositionWithJerk(state, target_position, jerk, time_elapsed);
}
void EmulateTilt(RotationalState* state, ControllerEmu::Tilt* const tilt_group, float time_elapsed)
@ -158,8 +102,8 @@ void EmulateSwing(MotionState* state, ControllerEmu::Force* swing_group, float t
// Note. Y/Z swapped because X/Y axis to the swing_group is X/Z to the wiimote.
// X is negated because Wiimote X+ is to the left.
ApproachPositionWithJerk(state, {-target.x, -target.z, target.y}, swing_group->GetMaxJerk(),
time_elapsed);
ApproachPositionWithJerk(state, {-target.x, -target.z, target.y},
Common::Vec3{1, 1, 1} * swing_group->GetMaxJerk(), time_elapsed);
// Just jump to our target angle scaled by our progress to the target position.
// TODO: If we wanted to be less hacky we could use ApproachAngleWithAccel.
@ -248,19 +192,19 @@ void ApproachAngleWithAccel(RotationalState* state, const Common::Vec3& angle_ta
}
void ApproachPositionWithJerk(PositionalState* state, const Common::Vec3& position_target,
float max_jerk, float time_elapsed)
const Common::Vec3& max_jerk, float time_elapsed)
{
const auto stop_distance =
Common::Vec3(CalculateStopDistance(state->velocity.x, state->acceleration.x, max_jerk),
CalculateStopDistance(state->velocity.y, state->acceleration.y, max_jerk),
CalculateStopDistance(state->velocity.z, state->acceleration.z, max_jerk));
Common::Vec3(CalculateStopDistance(state->velocity.x, state->acceleration.x, max_jerk.x),
CalculateStopDistance(state->velocity.y, state->acceleration.y, max_jerk.y),
CalculateStopDistance(state->velocity.z, state->acceleration.z, max_jerk.z));
const auto offset = position_target - state->position;
const auto stop_offset = offset - stop_distance;
const Common::Vec3 jerk{std::copysign(max_jerk, stop_offset.x),
std::copysign(max_jerk, stop_offset.y),
std::copysign(max_jerk, stop_offset.z)};
const Common::Vec3 jerk{std::copysign(max_jerk.x, stop_offset.x),
std::copysign(max_jerk.y, stop_offset.y),
std::copysign(max_jerk.z, stop_offset.z)};
state->acceleration += jerk * time_elapsed;

View File

@ -17,15 +17,6 @@ namespace WiimoteEmu
{
constexpr double GRAVITY_ACCELERATION = 9.80665;
// Used for dynamic shake
// TODO: kill this.
struct DynamicData
{
std::array<int, 3> timing; // Hold length in frames for each axis
std::array<double, 3> intensity; // Swing or shake intensity
std::array<int, 3> executing_frames_left; // Number of frames to execute the intensity operation
};
struct PositionalState
{
Common::Vec3 position;
@ -47,34 +38,13 @@ struct MotionState : PositionalState, RotationalState
// Build a rotational matrix from euler angles.
Common::Matrix33 GetRotationalMatrix(const Common::Vec3& angle);
void ApproachPositionWithJerk(PositionalState* state, const Common::Vec3& target, float max_jerk,
float time_elapsed);
void ApproachPositionWithJerk(PositionalState* state, const Common::Vec3& target,
const Common::Vec3& max_jerk, float time_elapsed);
void ApproachAngleWithAccel(RotationalState* state, const Common::Vec3& target, float max_accel,
float time_elapsed);
// Used for dynamic shake.
// This is used to pass in data that defines the dynamic action
// TODO: kill this
struct DynamicConfiguration
{
double low_intensity;
int frames_needed_for_low_intensity;
double med_intensity;
// Frames needed for med intensity can be calculated between high & low
double high_intensity;
int frames_needed_for_high_intensity;
int frames_to_execute; // How many frames should we execute the action for?
};
Common::Vec3 EmulateShake(ControllerEmu::Buttons* buttons_group, double intensity, u8* shake_step);
Common::Vec3 EmulateDynamicShake(DynamicData& dynamic_data, ControllerEmu::Buttons* buttons_group,
const DynamicConfiguration& config, u8* shake_step);
void EmulateShake(PositionalState* state, ControllerEmu::Shake* shake_group, float time_elapsed);
void EmulateTilt(RotationalState* state, ControllerEmu::Tilt* tilt_group, float time_elapsed);
void EmulateSwing(MotionState* state, ControllerEmu::Force* swing_group, float time_elapsed);

View File

@ -584,9 +584,7 @@ void Wiimote::DoState(PointerWrap& p)
// Dynamics
p.Do(m_swing_state);
p.Do(m_tilt_state);
// TODO: clean this up:
p.Do(m_shake_step);
p.Do(m_shake_state);
p.DoMarker("Wiimote");
}

View File

@ -12,7 +12,6 @@
#include "Common/Common.h"
#include "Common/CommonTypes.h"
#include "Common/MathUtil.h"
#include "Core/Config/WiimoteInputSettings.h"
#include "Core/HW/Wiimote.h"
#include "Core/HW/WiimoteEmu/WiimoteEmu.h"
@ -50,24 +49,10 @@ Nunchuk::Nunchuk() : EncryptedExtension(_trans("Nunchuk"))
// tilt
groups.emplace_back(m_tilt = new ControllerEmu::Tilt(_trans("Tilt")));
// shake
groups.emplace_back(m_shake = new ControllerEmu::Buttons(_trans("Shake")));
// i18n: Refers to a 3D axis (used when mapping motion controls)
m_shake->controls.emplace_back(new ControllerEmu::Input(ControllerEmu::Translate, _trans("X")));
// i18n: Refers to a 3D axis (used when mapping motion controls)
m_shake->controls.emplace_back(new ControllerEmu::Input(ControllerEmu::Translate, _trans("Y")));
// i18n: Refers to a 3D axis (used when mapping motion controls)
m_shake->controls.emplace_back(new ControllerEmu::Input(ControllerEmu::Translate, _trans("Z")));
groups.emplace_back(m_shake_soft = new ControllerEmu::Buttons("ShakeSoft"));
m_shake_soft->controls.emplace_back(new ControllerEmu::Input(ControllerEmu::DoNotTranslate, "X"));
m_shake_soft->controls.emplace_back(new ControllerEmu::Input(ControllerEmu::DoNotTranslate, "Y"));
m_shake_soft->controls.emplace_back(new ControllerEmu::Input(ControllerEmu::DoNotTranslate, "Z"));
groups.emplace_back(m_shake_hard = new ControllerEmu::Buttons("ShakeHard"));
m_shake_hard->controls.emplace_back(new ControllerEmu::Input(ControllerEmu::DoNotTranslate, "X"));
m_shake_hard->controls.emplace_back(new ControllerEmu::Input(ControllerEmu::DoNotTranslate, "Y"));
m_shake_hard->controls.emplace_back(new ControllerEmu::Input(ControllerEmu::DoNotTranslate, "Z"));
// Shake
// Inverse the default intensity so shake is opposite that of wiimote.
// This is needed by DKCR for proper shake action detection.
groups.emplace_back(m_shake = new ControllerEmu::Shake(_trans("Shake"), -1));
}
void Nunchuk::Update()
@ -104,6 +89,7 @@ void Nunchuk::Update()
// Acceleration data:
EmulateSwing(&m_swing_state, m_swing, 1.f / ::Wiimote::UPDATE_FREQ);
EmulateTilt(&m_tilt_state, m_tilt, 1.f / ::Wiimote::UPDATE_FREQ);
EmulateShake(&m_shake_state, m_shake, 1.f / ::Wiimote::UPDATE_FREQ);
const auto transformation =
GetRotationalMatrix(-m_tilt_state.angle) * GetRotationalMatrix(-m_swing_state.angle);
@ -112,12 +98,7 @@ void Nunchuk::Update()
Common::Vec3(0, 0, float(GRAVITY_ACCELERATION)));
// shake
accel += EmulateShake(m_shake, Config::Get(Config::NUNCHUK_INPUT_SHAKE_INTENSITY_MEDIUM),
m_shake_step.data());
accel += EmulateShake(m_shake_soft, Config::Get(Config::NUNCHUK_INPUT_SHAKE_INTENSITY_SOFT),
m_shake_soft_step.data());
accel += EmulateShake(m_shake_hard, Config::Get(Config::NUNCHUK_INPUT_SHAKE_INTENSITY_HARD),
m_shake_hard_step.data());
accel += m_shake_state.acceleration;
// 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);
@ -203,6 +184,7 @@ void Nunchuk::DoState(PointerWrap& p)
p.Do(m_swing_state);
p.Do(m_tilt_state);
p.Do(m_shake_state);
}
void Nunchuk::LoadDefaults(const ControllerInterface& ciface)
@ -227,5 +209,9 @@ void Nunchuk::LoadDefaults(const ControllerInterface& ciface)
m_buttons->SetControlExpression(0, "Control_L"); // C
m_buttons->SetControlExpression(1, "Shift_L"); // Z
#endif
// Shake
for (int i = 0; i < 3; ++i)
m_shake->SetControlExpression(i, "Click 2");
}
} // namespace WiimoteEmu

View File

@ -101,23 +101,14 @@ public:
private:
ControllerEmu::Tilt* m_tilt;
ControllerEmu::Force* m_swing;
ControllerEmu::Buttons* m_shake;
ControllerEmu::Buttons* m_shake_soft;
ControllerEmu::Buttons* m_shake_hard;
ControllerEmu::Shake* m_shake;
ControllerEmu::Buttons* m_buttons;
ControllerEmu::AnalogStick* m_stick;
// Dynamics:
MotionState m_swing_state;
RotationalState m_tilt_state;
// TODO: kill
std::array<u8, 3> m_shake_step{};
std::array<u8, 3> m_shake_soft_step{};
std::array<u8, 3> m_shake_hard_step{};
PositionalState m_shake_state;
};
} // namespace WiimoteEmu

View File

@ -17,7 +17,6 @@
#include "Common/MsgHandler.h"
#include "Core/Config/SYSCONFSettings.h"
#include "Core/Config/WiimoteInputSettings.h"
#include "Core/ConfigManager.h"
#include "Core/Core.h"
#include "Core/HW/Wiimote.h"
@ -130,11 +129,7 @@ void Wiimote::Reset()
// Dynamics:
m_swing_state = {};
m_tilt_state = {};
m_shake_step = {};
m_shake_soft_step = {};
m_shake_hard_step = {};
m_shake_dynamic_data = {};
m_shake_state = {};
}
Wiimote::Wiimote(const unsigned int index) : m_index(index)
@ -159,31 +154,7 @@ Wiimote::Wiimote(const unsigned int index) : m_index(index)
groups.emplace_back(m_tilt = new ControllerEmu::Tilt(_trans("Tilt")));
// shake
groups.emplace_back(m_shake = new ControllerEmu::Buttons(_trans("Shake")));
// i18n: Refers to a 3D axis (used when mapping motion controls)
m_shake->controls.emplace_back(new ControllerEmu::Input(ControllerEmu::Translate, _trans("X")));
// i18n: Refers to a 3D axis (used when mapping motion controls)
m_shake->controls.emplace_back(new ControllerEmu::Input(ControllerEmu::Translate, _trans("Y")));
// i18n: Refers to a 3D axis (used when mapping motion controls)
m_shake->controls.emplace_back(new ControllerEmu::Input(ControllerEmu::Translate, _trans("Z")));
groups.emplace_back(m_shake_soft = new ControllerEmu::Buttons("ShakeSoft"));
m_shake_soft->controls.emplace_back(new ControllerEmu::Input(ControllerEmu::DoNotTranslate, "X"));
m_shake_soft->controls.emplace_back(new ControllerEmu::Input(ControllerEmu::DoNotTranslate, "Y"));
m_shake_soft->controls.emplace_back(new ControllerEmu::Input(ControllerEmu::DoNotTranslate, "Z"));
groups.emplace_back(m_shake_hard = new ControllerEmu::Buttons("ShakeHard"));
m_shake_hard->controls.emplace_back(new ControllerEmu::Input(ControllerEmu::DoNotTranslate, "X"));
m_shake_hard->controls.emplace_back(new ControllerEmu::Input(ControllerEmu::DoNotTranslate, "Y"));
m_shake_hard->controls.emplace_back(new ControllerEmu::Input(ControllerEmu::DoNotTranslate, "Z"));
groups.emplace_back(m_shake_dynamic = new ControllerEmu::Buttons("Shake Dynamic"));
m_shake_dynamic->controls.emplace_back(
new ControllerEmu::Input(ControllerEmu::DoNotTranslate, "X"));
m_shake_dynamic->controls.emplace_back(
new ControllerEmu::Input(ControllerEmu::DoNotTranslate, "Y"));
m_shake_dynamic->controls.emplace_back(
new ControllerEmu::Input(ControllerEmu::DoNotTranslate, "Z"));
groups.emplace_back(m_shake = new ControllerEmu::Shake(_trans("Shake")));
// extension
groups.emplace_back(m_attachments = new ControllerEmu::Attachments(_trans("Extension")));
@ -687,6 +658,7 @@ void Wiimote::StepDynamics()
{
EmulateSwing(&m_swing_state, m_swing, 1.f / ::Wiimote::UPDATE_FREQ);
EmulateTilt(&m_tilt_state, m_tilt, 1.f / ::Wiimote::UPDATE_FREQ);
EmulateShake(&m_shake_state, m_shake, 1.f / ::Wiimote::UPDATE_FREQ);
// TODO: Move cursor state out of ControllerEmu::Cursor
// const auto cursor_mtx = EmulateCursorMovement(m_ir);
@ -710,24 +682,7 @@ Common::Vec3 Wiimote::GetAcceleration()
GetTransformation().Transform(
m_swing_state.acceleration + Common::Vec3(0, 0, float(GRAVITY_ACCELERATION)), 0);
DynamicConfiguration shake_config;
shake_config.low_intensity = Config::Get(Config::WIIMOTE_INPUT_SHAKE_INTENSITY_SOFT);
shake_config.med_intensity = Config::Get(Config::WIIMOTE_INPUT_SHAKE_INTENSITY_MEDIUM);
shake_config.high_intensity = Config::Get(Config::WIIMOTE_INPUT_SHAKE_INTENSITY_HARD);
shake_config.frames_needed_for_high_intensity =
Config::Get(Config::WIIMOTE_INPUT_SHAKE_DYNAMIC_FRAMES_HELD_HARD);
shake_config.frames_needed_for_low_intensity =
Config::Get(Config::WIIMOTE_INPUT_SHAKE_DYNAMIC_FRAMES_HELD_SOFT);
shake_config.frames_to_execute = Config::Get(Config::WIIMOTE_INPUT_SHAKE_DYNAMIC_FRAMES_LENGTH);
accel += EmulateShake(m_shake, Config::Get(Config::WIIMOTE_INPUT_SHAKE_INTENSITY_MEDIUM),
m_shake_step.data());
accel += EmulateShake(m_shake_soft, Config::Get(Config::WIIMOTE_INPUT_SHAKE_INTENSITY_SOFT),
m_shake_soft_step.data());
accel += EmulateShake(m_shake_hard, Config::Get(Config::WIIMOTE_INPUT_SHAKE_INTENSITY_HARD),
m_shake_hard_step.data());
accel += EmulateDynamicShake(m_shake_dynamic_data, m_shake_dynamic, shake_config,
m_shake_dynamic_step.data());
accel += m_shake_state.acceleration;
return accel;
}
@ -735,9 +690,10 @@ Common::Vec3 Wiimote::GetAcceleration()
Common::Matrix44 Wiimote::GetTransformation() const
{
// Includes positional and rotational effects of:
// IR, Swing, Tilt
// IR, Swing, Tilt, Shake
return Common::Matrix44::FromMatrix33(GetRotationalMatrix(-m_tilt_state.angle) *
return Common::Matrix44::Translate(-m_shake_state.position) *
Common::Matrix44::FromMatrix33(GetRotationalMatrix(-m_tilt_state.angle) *
GetRotationalMatrix(-m_swing_state.angle)) *
EmulateCursorMovement(m_ir) * Common::Matrix44::Translate(-m_swing_state.position);
}

View File

@ -222,10 +222,7 @@ private:
// Control groups for user input:
ControllerEmu::Buttons* m_buttons;
ControllerEmu::Buttons* m_dpad;
ControllerEmu::Buttons* m_shake;
ControllerEmu::Buttons* m_shake_soft;
ControllerEmu::Buttons* m_shake_hard;
ControllerEmu::Buttons* m_shake_dynamic;
ControllerEmu::Shake* m_shake;
ControllerEmu::Cursor* m_ir;
ControllerEmu::Tilt* m_tilt;
ControllerEmu::Force* m_swing;
@ -270,12 +267,6 @@ private:
// Dynamics:
MotionState m_swing_state;
RotationalState m_tilt_state;
// TODO: kill these:
std::array<u8, 3> m_shake_step{};
std::array<u8, 3> m_shake_soft_step{};
std::array<u8, 3> m_shake_hard_step{};
std::array<u8, 3> m_shake_dynamic_step{};
DynamicData m_shake_dynamic_data;
PositionalState m_shake_state;
};
} // namespace WiimoteEmu

View File

@ -74,7 +74,7 @@ static Common::Event g_compressAndDumpStateSyncEvent;
static std::thread g_save_thread;
// Don't forget to increase this after doing changes on the savestate system
static const u32 STATE_VERSION = 106; // Last changed in PR 7984
static const u32 STATE_VERSION = 107; // Last changed in PR 7952
// Maps savestate versions to Dolphin versions.
// Versions after 42 don't need to be added to this list,

View File

@ -514,6 +514,95 @@ void MappingIndicator::paintEvent(QPaintEvent*)
Settings::Instance().SetControllerStateNeeded(false);
}
ShakeMappingIndicator::ShakeMappingIndicator(ControllerEmu::Shake* group)
: MappingIndicator(group), m_shake_group(*group)
{
}
void ShakeMappingIndicator::paintEvent(QPaintEvent*)
{
Settings::Instance().SetControllerStateNeeded(true);
DrawShake();
Settings::Instance().SetControllerStateNeeded(false);
}
void ShakeMappingIndicator::DrawShake()
{
constexpr std::size_t HISTORY_COUNT = INDICATOR_UPDATE_FREQ;
WiimoteEmu::EmulateShake(&m_motion_state, &m_shake_group, 1.f / INDICATOR_UPDATE_FREQ);
constexpr float MAX_DISTANCE = 0.5f;
m_position_samples.push_front(m_motion_state.position / MAX_DISTANCE);
// This also holds the current state so +1.
if (m_position_samples.size() > HISTORY_COUNT + 1)
m_position_samples.pop_back();
// Bounding box size:
const double scale = height() / 2.5;
QPainter p(this);
p.translate(width() / 2, height() / 2);
// Bounding box.
p.setBrush(BBOX_BRUSH_COLOR);
p.setPen(BBOX_PEN_COLOR);
p.drawRect(-scale - 1, -scale - 1, scale * 2 + 1, scale * 2 + 1);
// UI y-axis is opposite that of acceleration Z.
p.scale(1.0, -1.0);
// Enable AA after drawing bounding box.
p.setRenderHint(QPainter::Antialiasing, true);
p.setRenderHint(QPainter::SmoothPixmapTransform, true);
// Deadzone.
p.setPen(DEADZONE_COLOR);
p.setBrush(DEADZONE_BRUSH);
p.drawRect(-scale, 0, scale * 2, m_shake_group.GetDeadzone() * scale);
// Raw input.
const auto raw_coord = m_shake_group.GetState(false);
p.setPen(Qt::NoPen);
p.setBrush(RAW_INPUT_COLOR);
for (std::size_t c = 0; c != raw_coord.data.size(); ++c)
{
p.drawEllipse(QPointF{-0.5 + c * 0.5, raw_coord.data[c]} * scale, INPUT_DOT_RADIUS,
INPUT_DOT_RADIUS);
}
// Grid line.
if (m_grid_line_position ||
std::any_of(m_position_samples.begin(), m_position_samples.end(),
[](const Common::Vec3& v) { return v.LengthSquared() != 0.0; }))
{
// Only start moving the line if there's non-zero data.
m_grid_line_position = (m_grid_line_position + 1) % HISTORY_COUNT;
}
const double grid_line_x = 1.0 - m_grid_line_position * 2.0 / HISTORY_COUNT;
p.setPen(RAW_INPUT_COLOR);
p.drawLine(QPointF{grid_line_x, -1.0} * scale, QPointF{grid_line_x, 1.0} * scale);
// Position history.
const QColor component_colors[] = {Qt::red, Qt::green, Qt::blue};
p.setBrush(Qt::NoBrush);
for (std::size_t c = 0; c != raw_coord.data.size(); ++c)
{
QPolygonF polyline;
int i = 0;
for (auto& sample : m_position_samples)
{
polyline.append(QPointF{1.0 - i * 2.0 / HISTORY_COUNT, sample.data[c]} * scale);
++i;
}
p.setPen(component_colors[c]);
p.drawPolyline(polyline);
}
}
void MappingIndicator::DrawCalibration(QPainter& p, Common::DVec2 point)
{
// TODO: Ugly magic number used in a few places in this file.

View File

@ -7,6 +7,8 @@
#include <QToolButton>
#include <QWidget>
#include <deque>
#include "Core/HW/WiimoteEmu/Dynamics.h"
#include "InputCommon/ControllerEmu/StickGate.h"
@ -31,6 +33,9 @@ public:
void SetCalibrationWidget(CalibrationWidget* widget);
protected:
WiimoteEmu::MotionState m_motion_state{};
private:
void DrawCursor(ControllerEmu::Cursor& cursor);
void DrawReshapableInput(ControllerEmu::ReshapableInput& stick);
@ -45,8 +50,21 @@ private:
ControllerEmu::ControlGroup* const m_group;
CalibrationWidget* m_calibration_widget{};
};
WiimoteEmu::MotionState m_motion_state{};
class ShakeMappingIndicator : public MappingIndicator
{
public:
explicit ShakeMappingIndicator(ControllerEmu::Shake* group);
void DrawShake();
void paintEvent(QPaintEvent*) override;
private:
std::deque<ControllerEmu::Shake::StateData> m_position_samples;
int m_grid_line_position = 0;
ControllerEmu::Shake& m_shake_group;
};
class CalibrationWidget : public QToolButton

View File

@ -85,7 +85,8 @@ QGroupBox* MappingWidget::CreateGroupBox(const QString& name, ControllerEmu::Con
group->type == ControllerEmu::GroupType::Stick ||
group->type == ControllerEmu::GroupType::Tilt ||
group->type == ControllerEmu::GroupType::MixedTriggers ||
group->type == ControllerEmu::GroupType::Force;
group->type == ControllerEmu::GroupType::Force ||
group->type == ControllerEmu::GroupType::Shake;
const bool need_calibration = group->type == ControllerEmu::GroupType::Cursor ||
group->type == ControllerEmu::GroupType::Stick ||
@ -129,7 +130,19 @@ QGroupBox* MappingWidget::CreateGroupBox(const QString& name, ControllerEmu::Con
if (need_indicator)
{
auto const indicator = new MappingIndicator(group);
MappingIndicator* indicator;
switch (group->type)
{
case ControllerEmu::GroupType::Shake:
indicator = new ShakeMappingIndicator(static_cast<ControllerEmu::Shake*>(group));
break;
default:
indicator = new MappingIndicator(group);
break;
}
connect(this, &MappingWidget::Update, indicator, QOverload<>::of(&MappingIndicator::update));
if (need_calibration)

View File

@ -4,8 +4,10 @@
#pragma once
#include <cmath>
#include <memory>
#include <string>
#include <type_traits>
#include <vector>
#include "Common/CommonTypes.h"
@ -35,7 +37,8 @@ enum class GroupType
Tilt,
Cursor,
Triggers,
Slider
Slider,
Shake,
};
class ControlGroup
@ -64,6 +67,12 @@ public:
void AddDeadzoneSetting(SettingValue<double>* value, double maximum_deadzone);
template <typename T>
static T ApplyDeadzone(T input, std::common_type_t<T> deadzone)
{
return std::copysign(std::max(T{0}, std::abs(input) - deadzone) / (T{1} - deadzone), input);
}
const std::string name;
const std::string ui_name;
const GroupType type;

View File

@ -104,8 +104,7 @@ Cursor::StateData Cursor::GetState(const bool adjusted)
const double max_z_step = STEP_Z_PER_SEC / 1000.0 * ms_since_update;
// Apply deadzone to z:
const ControlState deadzone = GetDeadzonePercentage();
z = std::copysign(std::max(0.0, std::abs(z) - deadzone) / (1.0 - deadzone), z);
z = ApplyDeadzone(z, GetDeadzonePercentage());
// Smooth out z movement:
// FYI: Not using relative input for Z.

View File

@ -71,8 +71,7 @@ Force::StateData Force::GetState(bool adjusted)
if (adjusted)
{
// Apply deadzone to z.
const ControlState deadzone = GetDeadzonePercentage();
z = std::copysign(std::max(0.0, std::abs(z) - deadzone) / (1.0 - deadzone), z);
z = ApplyDeadzone(z, GetDeadzonePercentage());
}
return {float(state.x), float(state.y), float(z)};
@ -105,4 +104,69 @@ ControlState Force::GetDefaultInputRadiusAtAngle(double) const
return 1.0;
}
Shake::Shake(const std::string& name_, ControlState default_intensity_scale)
: ControlGroup(name_, name_, GroupType::Shake)
{
// i18n: Refers to a 3D axis (used when mapping motion controls)
controls.emplace_back(new ControllerEmu::Input(ControllerEmu::Translate, _trans("X")));
// i18n: Refers to a 3D axis (used when mapping motion controls)
controls.emplace_back(new ControllerEmu::Input(ControllerEmu::Translate, _trans("Y")));
// i18n: Refers to a 3D axis (used when mapping motion controls)
controls.emplace_back(new ControllerEmu::Input(ControllerEmu::Translate, _trans("Z")));
AddDeadzoneSetting(&m_deadzone_setting, 50);
// Total travel distance in centimeters.
// Negative values can be used to reverse the initial direction of movement.
AddSetting(&m_intensity_setting,
// i18n: Refers to the intensity of shaking an emulated wiimote.
{_trans("Intensity"),
// i18n: The symbol/abbreviation for centimeters.
_trans("cm"),
// i18n: Refering to emulated wii remote movement.
_trans("Total travel distance.")},
10 * default_intensity_scale, -50, 50);
// Approximate number of up/down movements in one second.
AddSetting(&m_frequency_setting,
// i18n: Refers to a number of actions per second in Hz.
{_trans("Frequency"),
// i18n: The symbol/abbreviation for hertz (cycles per second).
_trans("Hz"),
// i18n: Refering to emulated wii remote movement.
_trans("Number of shakes per second.")},
6, 1, 20);
}
Shake::StateData Shake::GetState(bool adjusted) const
{
const float x = controls[0]->control_ref->State();
const float y = controls[1]->control_ref->State();
const float z = controls[2]->control_ref->State();
StateData result = {x, y, z};
// FYI: Unadjusted values are used in UI.
if (adjusted)
for (auto& c : result.data)
c = ApplyDeadzone(c, GetDeadzone());
return result;
}
ControlState Shake::GetDeadzone() const
{
return m_deadzone_setting.GetValue() / 100;
}
ControlState Shake::GetIntensity() const
{
return m_intensity_setting.GetValue() / 100;
}
ControlState Shake::GetFrequency() const
{
return m_frequency_setting.GetValue();
}
} // namespace ControllerEmu

View File

@ -40,4 +40,28 @@ private:
SettingValue<double> m_jerk_setting;
SettingValue<double> m_angle_setting;
};
class Shake : public ControlGroup
{
public:
using StateData = Common::Vec3;
explicit Shake(const std::string& name, ControlState default_intensity_scale = 1);
StateData GetState(bool adjusted = true) const;
ControlState GetDeadzone() const;
// Return total travel distance in meters.
ControlState GetIntensity() const;
// Return frequency in Hz.
ControlState GetFrequency() const;
private:
SettingValue<double> m_deadzone_setting;
SettingValue<double> m_intensity_setting;
SettingValue<double> m_frequency_setting;
};
} // namespace ControllerEmu

View File

@ -20,6 +20,8 @@ namespace ControllerEmu
MixedTriggers::MixedTriggers(const std::string& name_)
: ControlGroup(name_, GroupType::MixedTriggers)
{
AddDeadzoneSetting(&m_deadzone_setting, 25);
AddSetting(&m_threshold_setting,
{_trans("Threshold"),
// i18n: The percent symbol.
@ -27,8 +29,6 @@ MixedTriggers::MixedTriggers(const std::string& name_)
// i18n: Refers to the "threshold" setting for pressure sensitive gamepad inputs.
_trans("Input strength required for activation.")},
90, 0, 100);
AddDeadzoneSetting(&m_deadzone_setting, 25);
}
void MixedTriggers::GetState(u16* const digital, const u16* bitmasks, ControlState* analog,
@ -46,12 +46,9 @@ void MixedTriggers::GetState(u16* const digital, const u16* bitmasks, ControlSta
const int trigger_count = int(controls.size() / 2);
for (int i = 0; i != trigger_count; ++i)
{
ControlState button_value = controls[i]->control_ref->State();
ControlState analog_value = controls[trigger_count + i]->control_ref->State();
// Apply deadzone:
analog_value = std::max(0.0, analog_value - deadzone) / (1.0 - deadzone);
button_value = std::max(0.0, button_value - deadzone) / (1.0 - deadzone);
const ControlState button_value = ApplyDeadzone(controls[i]->control_ref->State(), deadzone);
ControlState analog_value =
ApplyDeadzone(controls[trigger_count + i]->control_ref->State(), deadzone);
// Apply threshold:
if (button_value > threshold)

View File

@ -34,9 +34,6 @@ Slider::StateData Slider::GetState()
const ControlState deadzone = m_deadzone_setting.GetValue() / 100;
const ControlState state = controls[1]->control_ref->State() - controls[0]->control_ref->State();
if (fabs(state) > deadzone)
return {(state - (deadzone * sign(state))) / (1 - deadzone)};
return {0.0};
return {ApplyDeadzone(state, deadzone)};
}
} // namespace ControllerEmu

View File

@ -28,7 +28,7 @@ Triggers::StateData Triggers::GetState()
StateData result(trigger_count);
for (size_t i = 0; i < trigger_count; ++i)
result.data[i] = std::max(controls[i]->control_ref->State() - deadzone, 0.0) / (1 - deadzone);
result.data[i] = ApplyDeadzone(controls[i]->control_ref->State(), deadzone);
return result;
}

View File

@ -272,8 +272,7 @@ ReshapableInput::ReshapeData ReshapableInput::Reshape(ControlState x, ControlSta
}
// Apply deadzone as a percentage of the user-defined calibration shape/size:
const ControlState deadzone = GetDeadzonePercentage();
dist = std::max(0.0, dist - deadzone) / (1.0 - deadzone);
dist = ApplyDeadzone(dist, GetDeadzonePercentage());
// Scale to the gate shape/radius:
dist *= gate_max_dist;