mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-18 23:07:07 -05:00
fix(libScePad): smooth a slow-reporting mouse over its measured report interval
This commit is contained in:
1 parent
cdb7ed0355
commit
06ca6a066b
4 files changed
+107
-2
No files matched your search
@@ -14,6 +14,10 @@ namespace Pad {
|
||||
|
||||
inline constexpr double MouseFullTiltCountsPerSecond = 3000.0;
|
||||
inline constexpr double MouseMinimumStepSeconds = 0.0005;
|
||||
inline constexpr double MouseRateWindowSeconds = 0.25;
|
||||
inline constexpr double MouseRateGapSeconds = 0.05;
|
||||
inline constexpr double MouseSmoothingReports = 3.0;
|
||||
inline constexpr double MouseSmoothingLimitMs = 30.0;
|
||||
|
||||
class MouseStick {
|
||||
public:
|
||||
@@ -22,16 +26,26 @@ public:
|
||||
void Advance(double seconds);
|
||||
void Reset();
|
||||
double Speed() const;
|
||||
double ReportRate() const;
|
||||
double SmoothingMs() const;
|
||||
std::array<double, 2> Deflection() const;
|
||||
|
||||
private:
|
||||
void measureReportRate(double step, bool moved);
|
||||
|
||||
InputSettings settings;
|
||||
double pendingX = 0.0;
|
||||
double pendingY = 0.0;
|
||||
double pendingSeconds = 0.0;
|
||||
double pendingCounts = 0.0;
|
||||
int pendingReports = 0;
|
||||
double speedX = 0.0;
|
||||
double speedY = 0.0;
|
||||
double idleSeconds = 0.0;
|
||||
double rateSeconds = 0.0;
|
||||
double rateCounts = 0.0;
|
||||
int rateReports = 0;
|
||||
double reportRate = 0.0;
|
||||
};
|
||||
|
||||
struct InputRequests {
|
||||
|
||||
@@ -9,6 +9,32 @@ Pad::MouseStick::MouseStick(const InputSettings& settings) : settings(settings)
|
||||
void Pad::MouseStick::Move(int deltaX, int deltaY) {
|
||||
pendingX += deltaX;
|
||||
pendingY += deltaY;
|
||||
pendingCounts += std::hypot(deltaX, deltaY);
|
||||
++pendingReports;
|
||||
}
|
||||
|
||||
void Pad::MouseStick::measureReportRate(double step, bool moved) {
|
||||
if (moved) {
|
||||
rateSeconds += std::min(idleSeconds + step, MouseRateGapSeconds);
|
||||
rateCounts += pendingCounts;
|
||||
rateReports += pendingReports;
|
||||
}
|
||||
pendingCounts = 0.0;
|
||||
pendingReports = 0;
|
||||
if (rateSeconds < MouseRateWindowSeconds) return;
|
||||
if (rateReports >= 10 && rateCounts >= 2.0 * rateReports) reportRate = rateReports / rateSeconds;
|
||||
rateSeconds = 0.0;
|
||||
rateCounts = 0.0;
|
||||
rateReports = 0;
|
||||
}
|
||||
|
||||
double Pad::MouseStick::ReportRate() const {
|
||||
return reportRate;
|
||||
}
|
||||
|
||||
double Pad::MouseStick::SmoothingMs() const {
|
||||
const double reports = reportRate > 0.0 ? std::min(MouseSmoothingReports * 1000.0 / reportRate, MouseSmoothingLimitMs) : 0.0;
|
||||
return settings.mouseSmoothingMs > 0.0 ? std::max(settings.mouseSmoothingMs, reports) : 0.0;
|
||||
}
|
||||
|
||||
void Pad::MouseStick::Advance(double seconds) {
|
||||
@@ -23,13 +49,15 @@ void Pad::MouseStick::Advance(double seconds) {
|
||||
const double targetY = pendingY * scale * settings.mouseVerticalScale;
|
||||
pendingX = 0.0;
|
||||
pendingY = 0.0;
|
||||
measureReportRate(step, moved);
|
||||
idleSeconds = moved ? 0.0 : idleSeconds + step;
|
||||
if (idleSeconds * 1000.0 >= settings.mouseDecayMs) {
|
||||
speedX = 0.0;
|
||||
speedY = 0.0;
|
||||
return;
|
||||
}
|
||||
const double keep = settings.mouseSmoothingMs > 0.0 ? std::exp(-step * 1000.0 / settings.mouseSmoothingMs) : 0.0;
|
||||
const double smoothing = SmoothingMs();
|
||||
const double keep = smoothing > 0.0 ? std::exp(-step * 1000.0 / smoothing) : 0.0;
|
||||
speedX = targetX + (speedX - targetX) * keep;
|
||||
speedY = targetY + (speedY - targetY) * keep;
|
||||
const double speed = std::hypot(speedX, speedY);
|
||||
@@ -43,6 +71,11 @@ void Pad::MouseStick::Reset() {
|
||||
pendingX = 0.0;
|
||||
pendingY = 0.0;
|
||||
pendingSeconds = 0.0;
|
||||
pendingCounts = 0.0;
|
||||
pendingReports = 0;
|
||||
rateSeconds = 0.0;
|
||||
rateCounts = 0.0;
|
||||
rateReports = 0;
|
||||
speedX = 0.0;
|
||||
speedY = 0.0;
|
||||
idleSeconds = 0.0;
|
||||
|
||||
@@ -182,6 +182,62 @@ void testSmoothing() {
|
||||
check(early > 0.5 * 0.55 && early < 0.5 * 0.72, "smoothing reaches about 63 percent after one time constant");
|
||||
}
|
||||
|
||||
void testSlowReportingMouse() {
|
||||
const double speed = 0.5 * Pad::MouseFullTiltCountsPerSecond;
|
||||
const double expected = expectedDeflection(defaults().settings, 0.5);
|
||||
for (const double reportHz : {125.0, 250.0, 1000.0}) {
|
||||
std::vector<TimedEvent> events{PadSimulation::MouseModeAt(0.0, true)};
|
||||
PadSimulation::AppendMotion(events, 0.0, 2000.0, speed, 0.0, reportHz);
|
||||
for (const double poll : {16.7, 8.3, 6.9, 1.0}) {
|
||||
const auto samples = Simulate(defaults(), events, poll, 2000.0);
|
||||
double low = 1.0;
|
||||
double high = 0.0;
|
||||
for (const auto& sample : samples) {
|
||||
if (sample.ms < 1000.0) continue;
|
||||
low = std::min(low, axis(sample.state.sticks[2]));
|
||||
high = std::max(high, axis(sample.state.sticks[2]));
|
||||
}
|
||||
check(high - low < 0.125, "a " + std::to_string(reportHz) + " Hz mouse at " + std::to_string(poll) + " ms polls wavers by " + std::to_string(high - low));
|
||||
check(std::abs((high + low) / 2.0 - expected) < 0.05, "a " + std::to_string(reportHz) + " Hz mouse at " + std::to_string(poll) + " ms polls stays around the expected tilt");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void testReportRateSmoothing() {
|
||||
for (const double reportHz : {125.0, 250.0, 1000.0}) {
|
||||
Pad::MouseStick mouse(defaults().settings);
|
||||
check(mouse.SmoothingMs() == defaults().settings.mouseSmoothingMs, "smoothing starts at the setting");
|
||||
const double periodSeconds = 1.0 / reportHz;
|
||||
for (int report = 0; report < static_cast<int>(reportHz); ++report) {
|
||||
mouse.Move(6, 0);
|
||||
mouse.Advance(periodSeconds);
|
||||
}
|
||||
check(std::abs(mouse.ReportRate() - reportHz) < 0.1 * reportHz, "measured " + std::to_string(mouse.ReportRate()) + " reports per second for a " + std::to_string(reportHz) + " Hz mouse");
|
||||
const double smoothing = mouse.SmoothingMs();
|
||||
check(std::abs(smoothing - std::max(10.0, 3000.0 / reportHz)) < 0.15 * smoothing, "smoothing " + std::to_string(smoothing) + " ms for a " + std::to_string(reportHz) + " Hz mouse");
|
||||
}
|
||||
Pad::MouseStick slow(defaults().settings);
|
||||
for (int step = 0; step < 40; ++step) {
|
||||
slow.Move(30, 0);
|
||||
slow.Advance(0.025);
|
||||
}
|
||||
check(std::abs(slow.ReportRate() - 40.0) < 2.0 && slow.SmoothingMs() == Pad::MouseSmoothingLimitMs, "smoothing for very slow reports stops at 30 ms");
|
||||
auto off = defaults().settings;
|
||||
off.mouseSmoothingMs = 0.0;
|
||||
Pad::MouseStick unsmoothed(off);
|
||||
for (int report = 0; report < 125; ++report) {
|
||||
unsmoothed.Move(6, 0);
|
||||
unsmoothed.Advance(0.008);
|
||||
}
|
||||
check(unsmoothed.SmoothingMs() == 0.0, "a smoothing of 0 stays off for a slow mouse");
|
||||
Pad::MouseStick single(defaults().settings);
|
||||
for (int report = 0; report < 125; ++report) {
|
||||
single.Move(1, 0);
|
||||
single.Advance(0.008);
|
||||
}
|
||||
check(single.ReportRate() == 0.0 && single.SmoothingMs() == defaults().settings.mouseSmoothingMs, "single-count reports are not taken for a slow mouse");
|
||||
}
|
||||
|
||||
std::vector<TimedEvent> waveMotion(double base, double swing) {
|
||||
constexpr double Pi = 3.14159265358979323846;
|
||||
std::vector<TimedEvent> events{PadSimulation::MouseModeAt(0.0, true)};
|
||||
@@ -341,6 +397,8 @@ int main() {
|
||||
{"decay", testDecay},
|
||||
{"max speed clamp", testMaxSpeedClamp},
|
||||
{"smoothing", testSmoothing},
|
||||
{"slow reporting mouse", testSlowReportingMouse},
|
||||
{"report rate smoothing", testReportRateSmoothing},
|
||||
{"poll rate independence", testPollRateIndependence},
|
||||
{"vertical scale", testVerticalScale},
|
||||
{"mouse mode off", testMouseModeOff},
|
||||
|
||||
@@ -67,7 +67,7 @@ The same file also accepts these settings, each at most once, in the form `Setti
|
||||
| `MouseCurve` | Number from 0.2 to 5, the response exponent. Above 1 gives finer control at low speed; below 1 reaches large tilts sooner. | `1` |
|
||||
| `MouseDeadzoneOffset` | Number from 0 to 0.9, the smallest tilt any movement produces as a fraction of full tilt. Set it just above the game's own deadzone: raise it if slow movement doesn't move the camera, lower it if small movements jump. | `0.2` |
|
||||
| `MouseMaxSpeed` | Number from 0.1 to 10, the highest tracked speed as a multiple of the full-tilt speed. Above 1, a fast flick keeps the stick at full tilt a little longer after the mouse stops. | `1` |
|
||||
| `MouseSmoothingMs` | Number from 0 to 500, the smoothing time constant in milliseconds. 0 follows every mouse report directly. | `10` |
|
||||
| `MouseSmoothingMs` | Number from 0 to 500, the smoothing time constant in milliseconds. A mouse that reports fewer than 300 times per second, such as a 125 or 250 Hz mouse, is smoothed over at least three of its report intervals, up to 30 ms; the report rate is measured from the movement itself. 0 follows every mouse report directly and turns this off too. | `10` |
|
||||
| `MouseDecayMs` | Number from 1 to 1000, the milliseconds without movement after which the stick is centered. | `30` |
|
||||
|
||||
```ini
|
||||
|
||||
Reference in new issue
Block a user