dolphin/Source/Core/InputCommon/ControllerEmu.h
Léo Lam 5cf07fdfbf Add relative input for the Wiimote IR
This adds an option to enable relative input for the Wiimote IR
as described in issue 9014.

Enabling it will result in the pointer not going back to the centre
and the inputs will control the direction, not the absolute position.

Also adds a Dead Zone setting which is really needed when relative
input is enabled to prevent the cursor from slowly drifting on
most controllers. (Note: the Deadzone setting has no effect when
relative input is disabled)
2016-09-24 15:57:46 +02:00

498 lines
14 KiB
C++

// Copyright 2010 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#pragma once
#include <algorithm>
#include <cmath>
#include <memory>
#include <mutex>
#include <string>
#include <vector>
#include "Common/IniFile.h"
#include "Common/MathUtil.h"
#include "Core/ConfigManager.h"
#include "InputCommon/ControllerInterface/ControllerInterface.h"
#include "InputCommon/GCPadStatus.h"
#define sign(x) ((x) ? (x) < 0 ? -1 : 1 : 0)
enum
{
GROUP_TYPE_OTHER,
GROUP_TYPE_STICK,
GROUP_TYPE_MIXED_TRIGGERS,
GROUP_TYPE_BUTTONS,
GROUP_TYPE_FORCE,
GROUP_TYPE_EXTENSION,
GROUP_TYPE_TILT,
GROUP_TYPE_CURSOR,
GROUP_TYPE_TRIGGERS,
GROUP_TYPE_SLIDER
};
enum
{
SETTING_RADIUS,
SETTING_DEADZONE,
};
const char* const named_directions[] = {"Up", "Down", "Left", "Right"};
class ControllerEmu
{
public:
class ControlGroup
{
public:
class Control
{
protected:
Control(ControllerInterface::ControlReference* const _ref, const std::string& _name)
: control_ref(_ref), name(_name)
{
}
public:
virtual ~Control() {}
std::unique_ptr<ControllerInterface::ControlReference> const control_ref;
const std::string name;
};
class Input : public Control
{
public:
Input(const std::string& _name) : Control(new ControllerInterface::InputReference, _name) {}
};
class Output : public Control
{
public:
Output(const std::string& _name) : Control(new ControllerInterface::OutputReference, _name) {}
};
enum class SettingType
{
NORMAL, // normal settings are saved to configuration files
VIRTUAL, // virtual settings are not saved at all
};
class NumericSetting
{
public:
NumericSetting(const std::string& setting_name, const ControlState default_value,
const unsigned int low = 0, const unsigned int high = 100,
const SettingType setting_type = SettingType::NORMAL)
: m_type(setting_type), m_name(setting_name), m_default_value(default_value), m_low(low),
m_high(high)
{
}
ControlState GetValue() const { return m_value; }
void SetValue(ControlState value) { m_value = value; }
const SettingType m_type;
const std::string m_name;
const ControlState m_default_value;
const unsigned int m_low, m_high;
ControlState m_value;
};
class BooleanSetting
{
public:
BooleanSetting(const std::string& setting_name, const bool default_value,
const SettingType setting_type = SettingType::NORMAL)
: m_type(setting_type), m_name(setting_name), m_default_value(default_value)
{
}
virtual bool GetValue() const { return m_value; }
virtual void SetValue(bool value) { m_value = value; }
const SettingType m_type;
const std::string m_name;
const bool m_default_value;
bool m_value;
};
class BackgroundInputSetting : public BooleanSetting
{
public:
BackgroundInputSetting(const std::string& setting_name)
: BooleanSetting(setting_name, false, SettingType::VIRTUAL)
{
}
bool GetValue() const override { return SConfig::GetInstance().m_BackgroundInput; }
void SetValue(bool value) override
{
m_value = value;
SConfig::GetInstance().m_BackgroundInput = value;
}
};
ControlGroup(const std::string& _name, const unsigned int _type = GROUP_TYPE_OTHER)
: name(_name), ui_name(_name), type(_type)
{
}
ControlGroup(const std::string& _name, const std::string& _ui_name,
const unsigned int _type = GROUP_TYPE_OTHER)
: name(_name), ui_name(_ui_name), type(_type)
{
}
virtual ~ControlGroup() {}
virtual void LoadConfig(IniFile::Section* sec, const std::string& defdev = "",
const std::string& base = "");
virtual void SaveConfig(IniFile::Section* sec, const std::string& defdev = "",
const std::string& base = "");
void SetControlExpression(int index, const std::string& expression);
const std::string name;
const std::string ui_name;
const unsigned int type;
std::vector<std::unique_ptr<Control>> controls;
std::vector<std::unique_ptr<NumericSetting>> numeric_settings;
std::vector<std::unique_ptr<BooleanSetting>> boolean_settings;
};
class AnalogStick : public ControlGroup
{
public:
// The GameCube controller and Wiimote attachments have a different default radius
AnalogStick(const char* const _name, ControlState default_radius);
AnalogStick(const char* const _name, const char* const _ui_name, ControlState default_radius);
void GetState(ControlState* const x, ControlState* const y)
{
ControlState yy = controls[0]->control_ref->State() - controls[1]->control_ref->State();
ControlState xx = controls[3]->control_ref->State() - controls[2]->control_ref->State();
ControlState radius = numeric_settings[SETTING_RADIUS]->GetValue();
ControlState deadzone = numeric_settings[SETTING_DEADZONE]->GetValue();
ControlState m = controls[4]->control_ref->State();
ControlState ang = atan2(yy, xx);
ControlState ang_sin = sin(ang);
ControlState ang_cos = cos(ang);
ControlState dist = sqrt(xx * xx + yy * yy);
// dead zone code
dist = std::max(0.0, dist - deadzone);
dist /= (1 - deadzone);
// radius
dist *= radius;
// The modifier halves the distance by 50%, which is useful
// for keyboard controls.
if (m)
dist *= 0.5;
yy = std::max(-1.0, std::min(1.0, ang_sin * dist));
xx = std::max(-1.0, std::min(1.0, ang_cos * dist));
*y = yy;
*x = xx;
}
};
class Buttons : public ControlGroup
{
public:
Buttons(const std::string& _name);
template <typename C>
void GetState(C* const buttons, const C* bitmasks)
{
for (auto& control : controls)
{
if (control->control_ref->State() > numeric_settings[0]->GetValue()) // threshold
*buttons |= *bitmasks;
bitmasks++;
}
}
};
class ModifySettingsButton : public Buttons
{
public:
ModifySettingsButton(std::string button_name);
void AddInput(std::string button_name, bool toggle = false);
void GetState();
const std::vector<bool>& isSettingToggled() const { return associated_settings_toggle; }
const std::vector<bool>& getSettingsModifier() const { return associated_settings; }
private:
std::vector<bool> threshold_exceeded; // internal calculation (if "state" was above threshold)
std::vector<bool> associated_settings_toggle; // is setting toggled or hold?
std::vector<bool> associated_settings; // result
};
class MixedTriggers : public ControlGroup
{
public:
MixedTriggers(const std::string& _name);
void GetState(u16* const digital, const u16* bitmasks, ControlState* analog)
{
const unsigned int trig_count = ((unsigned int)(controls.size() / 2));
for (unsigned int i = 0; i < trig_count; ++i, ++bitmasks, ++analog)
{
if (controls[i]->control_ref->State() > numeric_settings[0]->GetValue()) // threshold
{
*analog = 1.0;
*digital |= *bitmasks;
}
else
{
*analog = controls[i + trig_count]->control_ref->State();
}
}
}
};
class Triggers : public ControlGroup
{
public:
Triggers(const std::string& _name);
void GetState(ControlState* analog)
{
const unsigned int trig_count = ((unsigned int)(controls.size()));
const ControlState deadzone = numeric_settings[0]->GetValue();
for (unsigned int i = 0; i < trig_count; ++i, ++analog)
*analog = std::max(controls[i]->control_ref->State() - deadzone, 0.0) / (1 - deadzone);
}
};
class Slider : public ControlGroup
{
public:
Slider(const std::string& _name);
void GetState(ControlState* const slider)
{
const ControlState deadzone = numeric_settings[0]->GetValue();
const ControlState state =
controls[1]->control_ref->State() - controls[0]->control_ref->State();
if (fabs(state) > deadzone)
*slider = (state - (deadzone * sign(state))) / (1 - deadzone);
else
*slider = 0;
}
};
class Force : public ControlGroup
{
public:
Force(const std::string& _name);
void GetState(ControlState* axis)
{
const ControlState deadzone = numeric_settings[0]->GetValue();
for (unsigned int i = 0; i < 6; i += 2)
{
ControlState tmpf = 0;
const ControlState state =
controls[i + 1]->control_ref->State() - controls[i]->control_ref->State();
if (fabs(state) > deadzone)
tmpf = ((state - (deadzone * sign(state))) / (1 - deadzone));
ControlState& ax = m_swing[i >> 1];
*axis++ = (tmpf - ax);
ax = tmpf;
}
}
private:
ControlState m_swing[3];
};
class Tilt : public ControlGroup
{
public:
Tilt(const std::string& _name);
void GetState(ControlState* const x, ControlState* const y, const bool step = true)
{
// this is all a mess
ControlState yy = controls[0]->control_ref->State() - controls[1]->control_ref->State();
ControlState xx = controls[3]->control_ref->State() - controls[2]->control_ref->State();
ControlState deadzone = numeric_settings[0]->GetValue();
ControlState circle = numeric_settings[1]->GetValue();
auto const angle = numeric_settings[2]->GetValue() / 1.8;
ControlState m = controls[4]->control_ref->State();
// deadzone / circle stick code
// this section might be all wrong, but its working good enough, I think
ControlState ang = atan2(yy, xx);
ControlState ang_sin = sin(ang);
ControlState ang_cos = cos(ang);
// the amt a full square stick would have at current angle
ControlState square_full =
std::min(ang_sin ? 1 / fabs(ang_sin) : 2, ang_cos ? 1 / fabs(ang_cos) : 2);
// the amt a full stick would have that was (user setting circular) at current angle
// I think this is more like a pointed circle rather than a rounded square like it should be
ControlState stick_full = (square_full * (1 - circle)) + (circle);
ControlState dist = sqrt(xx * xx + yy * yy);
// dead zone code
dist = std::max(0.0, dist - deadzone * stick_full);
dist /= (1 - deadzone);
// circle stick code
ControlState amt = dist / stick_full;
dist += (square_full - 1) * amt * circle;
if (m)
dist *= 0.5;
yy = std::max(-1.0, std::min(1.0, ang_sin * dist));
xx = std::max(-1.0, std::min(1.0, ang_cos * dist));
// this is kinda silly here
// gui being open will make this happen 2x as fast, o well
// silly
if (step)
{
if (xx > m_tilt[0])
m_tilt[0] = std::min(m_tilt[0] + 0.1, xx);
else if (xx < m_tilt[0])
m_tilt[0] = std::max(m_tilt[0] - 0.1, xx);
if (yy > m_tilt[1])
m_tilt[1] = std::min(m_tilt[1] + 0.1, yy);
else if (yy < m_tilt[1])
m_tilt[1] = std::max(m_tilt[1] - 0.1, yy);
}
*y = m_tilt[1] * angle;
*x = m_tilt[0] * angle;
}
private:
ControlState m_tilt[2];
};
class Cursor : public ControlGroup
{
public:
Cursor(const std::string& _name);
void GetState(ControlState* const x, ControlState* const y, ControlState* const z,
const bool adjusted = false)
{
const ControlState zz = controls[4]->control_ref->State() - controls[5]->control_ref->State();
// silly being here
if (zz > m_z)
m_z = std::min(m_z + 0.1, zz);
else if (zz < m_z)
m_z = std::max(m_z - 0.1, zz);
*z = m_z;
// hide
if (controls[6]->control_ref->State() > 0.5)
{
*x = 10000;
*y = 0;
}
else
{
ControlState yy = controls[0]->control_ref->State() - controls[1]->control_ref->State();
ControlState xx = controls[3]->control_ref->State() - controls[2]->control_ref->State();
// adjust cursor according to settings
if (adjusted)
{
xx *= (numeric_settings[1]->GetValue() * 2);
yy *= (numeric_settings[2]->GetValue() * 2);
yy += (numeric_settings[0]->GetValue() - 0.5);
}
// relative input
if (boolean_settings[0]->GetValue())
{
const ControlState deadzone = numeric_settings[3]->GetValue();
// deadzone to avoid the cursor slowly drifting
if (std::abs(xx) > deadzone)
m_x = MathUtil::Clamp(m_x + xx * SPEED_MULTIPLIER, -1.0, 1.0);
if (std::abs(yy) > deadzone)
m_y = MathUtil::Clamp(m_y + yy * SPEED_MULTIPLIER, -1.0, 1.0);
}
else
{
m_x = xx;
m_y = yy;
}
*x = m_x;
*y = m_y;
}
}
ControlState m_z;
private:
// This is used to reduce the cursor speed for relative input
// to something that makes sense with the default range.
static constexpr double SPEED_MULTIPLIER = 0.04;
ControlState m_x = 0.0;
ControlState m_y = 0.0;
};
class Extension : public ControlGroup
{
public:
Extension(const std::string& _name)
: ControlGroup(_name, GROUP_TYPE_EXTENSION), switch_extension(0), active_extension(0)
{
}
~Extension() {}
void GetState(u8* const data);
bool IsButtonPressed() const;
std::vector<std::unique_ptr<ControllerEmu>> attachments;
int switch_extension;
int active_extension;
};
virtual ~ControllerEmu() {}
virtual std::string GetName() const = 0;
virtual void LoadDefaults(const ControllerInterface& ciface);
virtual void LoadConfig(IniFile::Section* sec, const std::string& base = "");
virtual void SaveConfig(IniFile::Section* sec, const std::string& base = "");
void UpdateDefaultDevice();
void UpdateReferences(ControllerInterface& devi);
// This returns a lock that should be held before calling State() on any control
// references and GetState(), by extension. This prevents a race condition
// which happens while handling a hotplug event because a control reference's State()
// could be called before we have finished updating the reference.
static std::unique_lock<std::recursive_mutex> GetStateLock();
std::vector<std::unique_ptr<ControlGroup>> groups;
ciface::Core::DeviceQualifier default_device;
};