Skip to content

Commit

Permalink
Merge pull request dolphin-emu#13197 from jordan-woyak/vrr-mapping-in…
Browse files Browse the repository at this point in the history
…dicators

DolphinQt: Update mapping indicators at screen refresh rate.
  • Loading branch information
JMC47 authored Dec 3, 2024
2 parents cf29214 + e7a8e2f commit 26ba8f5
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 53 deletions.
119 changes: 74 additions & 45 deletions Source/Core/DolphinQt/Config/Mapping/MappingIndicator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

#include <array>
#include <cmath>
#include <numeric>

#include <fmt/format.h>

Expand All @@ -19,12 +18,10 @@

#include "Core/HW/WiimoteEmu/Camera.h"

#include "InputCommon/ControlReference/ControlReference.h"
#include "InputCommon/ControllerEmu/Control/Control.h"
#include "InputCommon/ControllerEmu/ControlGroup/Cursor.h"
#include "InputCommon/ControllerEmu/ControlGroup/Force.h"
#include "InputCommon/ControllerEmu/ControlGroup/MixedTriggers.h"
#include "InputCommon/ControllerEmu/Setting/NumericSetting.h"
#include "InputCommon/ControllerInterface/CoreDevice.h"

#include "DolphinQt/Config/Mapping/MappingWidget.h"
Expand Down Expand Up @@ -289,7 +286,14 @@ void GenerateFibonacciSphere(int point_count, F&& callback)

void MappingIndicator::paintEvent(QPaintEvent*)
{
constexpr float max_elapsed_seconds = 0.1f;

const auto now = Clock::now();
const float elapsed_seconds = std::chrono::duration_cast<DT_s>(now - m_last_update).count();
m_last_update = now;

const auto lock = ControllerEmu::EmulatedController::GetStateLock();
Update(std::min(elapsed_seconds, max_elapsed_seconds));
Draw();
}

Expand Down Expand Up @@ -321,7 +325,7 @@ void SquareIndicator::TransformPainter(QPainter& p)
p.setRenderHint(QPainter::Antialiasing, true);
p.setRenderHint(QPainter::SmoothPixmapTransform, true);

p.translate(width() / 2, height() / 2);
p.translate(width() / 2.0, height() / 2.0);
const auto scale = GetContentsScale();
p.scale(scale, scale);
}
Expand Down Expand Up @@ -413,10 +417,13 @@ void AnalogStickIndicator::Draw()
(adj_coord.x || adj_coord.y) ? std::make_optional(adj_coord) : std::nullopt);
}

void TiltIndicator::Draw()
void TiltIndicator::Update(float elapsed_seconds)
{
WiimoteEmu::EmulateTilt(&m_motion_state, &m_group, 1.f / INDICATOR_UPDATE_FREQ);
WiimoteEmu::EmulateTilt(&m_motion_state, &m_group, elapsed_seconds);
}

void TiltIndicator::Draw()
{
auto adj_coord = Common::DVec2{-m_motion_state.angle.y, m_motion_state.angle.x} / MathUtil::PI;

// Angle values after dividing by pi.
Expand Down Expand Up @@ -564,28 +571,54 @@ void SwingIndicator::DrawUnderGate(QPainter& p)
}
}

void SwingIndicator::Draw()
void SwingIndicator::Update(float elapsed_seconds)
{
auto& force = m_swing_group;
WiimoteEmu::EmulateSwing(&m_motion_state, &force, 1.f / INDICATOR_UPDATE_FREQ);
WiimoteEmu::EmulateSwing(&m_motion_state, &m_swing_group, elapsed_seconds);
}

DrawReshapableInput(force, SWING_GATE_COLOR,
void SwingIndicator::Draw()
{
DrawReshapableInput(m_swing_group, SWING_GATE_COLOR,
Common::DVec2{-m_motion_state.position.x, m_motion_state.position.z});
}

void ShakeMappingIndicator::Draw()
void ShakeMappingIndicator::Update(float elapsed_seconds)
{
constexpr std::size_t HISTORY_COUNT = INDICATOR_UPDATE_FREQ;
WiimoteEmu::EmulateShake(&m_motion_state, &m_shake_group, elapsed_seconds);

WiimoteEmu::EmulateShake(&m_motion_state, &m_shake_group, 1.f / INDICATOR_UPDATE_FREQ);
for (auto& sample : m_position_samples)
sample.age += elapsed_seconds;

m_position_samples.erase(
std::ranges::find_if(m_position_samples,
[](const ShakeSample& sample) { return sample.age > 1.f; }),
m_position_samples.end());

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();
m_position_samples.push_front(ShakeSample{m_motion_state.position / MAX_DISTANCE});

const bool any_non_zero_samples =
std::any_of(m_position_samples.begin(), m_position_samples.end(),
[](const ShakeSample& s) { return s.state.LengthSquared() != 0.0; });

// Only start moving the line if there's non-zero data.
if (m_grid_line_position || any_non_zero_samples)
{
m_grid_line_position += elapsed_seconds;

if (m_grid_line_position > 1.f)
{
if (any_non_zero_samples)
m_grid_line_position = std::fmod(m_grid_line_position, 1.f);
else
m_grid_line_position = 0;
}
}
}

void ShakeMappingIndicator::Draw()
{
QPainter p(this);
DrawBoundingBox(p);
TransformPainter(p);
Expand All @@ -610,15 +643,7 @@ void ShakeMappingIndicator::Draw()
p.drawPoint(QPointF{-0.5 + c * 0.5, raw_coord.data[c]});
}

// 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;
const double grid_line_x = 1.0 - m_grid_line_position * 2.0;
p.setPen(QPen(GetRawInputColor(), 0));
p.drawLine(QPointF{grid_line_x, -1.0}, QPointF{grid_line_x, 1.0});

Expand All @@ -629,12 +654,8 @@ void ShakeMappingIndicator::Draw()
{
QPolygonF polyline;

int i = 0;
for (auto& sample : m_position_samples)
{
polyline.append(QPointF{1.0 - i * 2.0 / HISTORY_COUNT, sample.data[c]});
++i;
}
polyline.append(QPointF{1.0 - sample.age * 2.0, sample.state.data[c]});

p.setPen(QPen(component_colors[c], 0));
p.drawPolyline(polyline);
Expand Down Expand Up @@ -692,7 +713,7 @@ void AccelerometerMappingIndicator::Draw()
p.setBrush(Qt::NoBrush);

p.resetTransform();
p.translate(width() / 2, height() / 2);
p.translate(width() / 2.0, height() / 2.0);

// Red dot upright target.
p.setPen(GetAdjustedInputColor());
Expand All @@ -717,30 +738,38 @@ void AccelerometerMappingIndicator::Draw()
fmt::format("{:.2f} g", state.Length() / WiimoteEmu::GRAVITY_ACCELERATION)));
}

void GyroMappingIndicator::Draw()
void GyroMappingIndicator::Update(float elapsed_seconds)
{
const auto gyro_state = m_gyro_group.GetState();
const auto raw_gyro_state = m_gyro_group.GetRawState();
const auto angular_velocity = gyro_state.value_or(Common::Vec3{});
const auto jitter = raw_gyro_state - m_previous_velocity;
m_previous_velocity = raw_gyro_state;

m_state *= WiimoteEmu::GetRotationFromGyroscope(angular_velocity * Common::Vec3(-1, +1, -1) /
INDICATOR_UPDATE_FREQ);
m_state *= WiimoteEmu::GetRotationFromGyroscope(angular_velocity * Common::Vec3(-1, +1, -1) *
elapsed_seconds);
m_state = m_state.Normalized();

// Reset orientation when stable for a bit:
constexpr u32 STABLE_RESET_STEPS = INDICATOR_UPDATE_FREQ;
constexpr float STABLE_RESET_SECONDS = 1.f;
// Consider device stable when data (with deadzone applied) is zero.
const bool is_stable = !angular_velocity.LengthSquared();

if (!is_stable)
m_stable_steps = 0;
else if (m_stable_steps != STABLE_RESET_STEPS)
++m_stable_steps;
m_stable_time = 0;
else if (m_stable_time < STABLE_RESET_SECONDS)
m_stable_time += elapsed_seconds;

if (STABLE_RESET_STEPS == m_stable_steps)
if (m_stable_time >= STABLE_RESET_SECONDS)
m_state = Common::Quaternion::Identity();
}

void GyroMappingIndicator::Draw()
{
const auto gyro_state = m_gyro_group.GetState();
const auto raw_gyro_state = m_gyro_group.GetRawState();
const auto angular_velocity = gyro_state.value_or(Common::Vec3{});
const auto jitter = raw_gyro_state - m_previous_velocity;
m_previous_velocity = raw_gyro_state;

// Consider device stable when data (with deadzone applied) is zero.
const bool is_stable = !angular_velocity.LengthSquared();

// Use an empty rotation matrix if gyroscope data is not present.
const auto rotation =
Expand Down Expand Up @@ -814,7 +843,7 @@ void GyroMappingIndicator::Draw()
p.setBrush(Qt::NoBrush);

p.resetTransform();
p.translate(width() / 2, height() / 2);
p.translate(width() / 2.0, height() / 2.0);

// Red dot upright target.
p.setPen(GetAdjustedInputColor());
Expand Down
20 changes: 17 additions & 3 deletions Source/Core/DolphinQt/Config/Mapping/MappingIndicator.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,12 @@ class MappingIndicator : public QWidget

protected:
virtual void Draw() {}
virtual void Update(float elapsed_seconds) {}

private:
void paintEvent(QPaintEvent*) override;

Clock::time_point m_last_update = Clock::now();
};

class SquareIndicator : public MappingIndicator
Expand Down Expand Up @@ -98,6 +101,7 @@ class TiltIndicator : public ReshapableInputIndicator

private:
void Draw() override;
void Update(float elapsed_seconds) override;

ControllerEmu::Tilt& m_group;
WiimoteEmu::MotionState m_motion_state{};
Expand Down Expand Up @@ -132,6 +136,7 @@ class SwingIndicator : public ReshapableInputIndicator

private:
void Draw() override;
void Update(float elapsed_seconds) override;

void DrawUnderGate(QPainter& p) override;

Expand All @@ -146,11 +151,19 @@ class ShakeMappingIndicator : public SquareIndicator

private:
void Draw() override;
void Update(float elapsed_seconds) override;

ControllerEmu::Shake& m_shake_group;
WiimoteEmu::MotionState m_motion_state{};
std::deque<ControllerEmu::Shake::StateData> m_position_samples;
int m_grid_line_position = 0;

struct ShakeSample
{
ControllerEmu::Shake::StateData state;
float age = 0.f;
};

std::deque<ShakeSample> m_position_samples;
float m_grid_line_position = 0;
};

class AccelerometerMappingIndicator : public SquareIndicator
Expand All @@ -174,11 +187,12 @@ class GyroMappingIndicator : public SquareIndicator

private:
void Draw() override;
void Update(float elapsed_seconds) override;

ControllerEmu::IMUGyroscope& m_gyro_group;
Common::Quaternion m_state = Common::Quaternion::Identity();
Common::Vec3 m_previous_velocity = {};
u32 m_stable_steps = 0;
float m_stable_time = 0;
};

class IRPassthroughMappingIndicator : public SquareIndicator
Expand Down
2 changes: 0 additions & 2 deletions Source/Core/DolphinQt/Config/Mapping/MappingWidget.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,6 @@ class NumericSettingBase;
enum class SettingVisibility;
} // namespace ControllerEmu

constexpr int INDICATOR_UPDATE_FREQ = 30;

class MappingWidget : public QWidget
{
Q_OBJECT
Expand Down
9 changes: 6 additions & 3 deletions Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@
#include <QGroupBox>
#include <QHBoxLayout>
#include <QPushButton>
#include <QScreen>
#include <QTabWidget>
#include <QTimer>
#include <QToolButton>
#include <QVBoxLayout>

#include "Core/Core.h"
#include "Core/HotkeyManager.h"

#include "Common/CommonPaths.h"
Expand Down Expand Up @@ -73,12 +73,15 @@ MappingWindow::MappingWindow(QWidget* parent, Type type, int port_num)
SetMappingType(type);

const auto timer = new QTimer(this);
connect(timer, &QTimer::timeout, this, [this] {
connect(timer, &QTimer::timeout, this, [this, timer] {
const double refresh_rate = screen()->refreshRate();
timer->setInterval(1000 / refresh_rate);

const auto lock = GetController()->GetStateLock();
emit Update();
});

timer->start(1000 / INDICATOR_UPDATE_FREQ);
timer->start(100);

const auto lock = GetController()->GetStateLock();
emit ConfigChanged();
Expand Down

0 comments on commit 26ba8f5

Please sign in to comment.