steam-deck-tools/SteamController/Devices/MouseControllerFauxLizard.cs

211 lines
7.6 KiB
C#
Raw Normal View History

using static SteamController.Devices.SteamController;
namespace SteamController.Devices
{
public partial class MouseController : IDisposable
{
// Adjustable parameters (tweakable settings)
private const int bufferSize = 4; // Input smoothing buffer size
private const int gestureRadius = 30; // Gesture commit threshold (pixels)
private const double gestureFlushRate = 0.1; // Rate at which gesture flush decays
private const int velocityWindowSize = 20; // Velocity smoothing window size
private const double minGlideMagnitude = 0.8; // Minimum velocity to start glide
private const double minGlideVelocity = 0.15; // Minimum velocity to continue glide
private const double hapticTriggerDelta = 30; // Distance threshold for haptic feedback on each pad
// Functional constants (derived once)
private readonly double gestureRadiusSq = gestureRadius * gestureRadius;
private readonly double minGlideMagnitudeSq = minGlideMagnitude * minGlideMagnitude;
private readonly double minGlideVelocitySq = minGlideVelocity * minGlideVelocity;
// Runtime state RPad
private Queue<double> bufX = new(), bufY = new();
private double totalDeltaX = 0, totalDeltaY = 0;
private bool gestureCommitted = false;
private double gestureFlushX = 0, gestureFlushY = 0;
private bool isGliding = false;
private DateTime releaseTime;
private double releaseVelocityX = 0, releaseVelocityY = 0;
private Queue<double> velocityHistoryX = new(), velocityHistoryY = new();
private double velocitySumX = 0, velocitySumY = 0;
// Runtime state haptics
private double hapticDeltaR = 0;
private double hapticDeltaL = 0;
// Main movement logic
public void MoveByFauxLizard(double dx, double dy, bool isTouched)
{
if (!isTouched)
{
// Start glide if gesture was committed and velocity is high enough
if (!isGliding && gestureCommitted)
{
double magSq = releaseVelocityX * releaseVelocityX + releaseVelocityY * releaseVelocityY;
if (magSq >= minGlideMagnitudeSq)
{
releaseTime = DateTime.Now;
isGliding = true;
}
}
// Continue glide if active
if (isGliding)
{
double elapsed = (DateTime.Now - releaseTime).TotalSeconds;
double glideX = ApplyReleaseGlide(releaseVelocityX, elapsed);
double glideY = ApplyReleaseGlide(releaseVelocityY, elapsed);
double glideMagSq = glideX * glideX + glideY * glideY;
if (glideMagSq < minGlideVelocitySq)
{
isGliding = false;
releaseVelocityX = releaseVelocityY = 0;
}
else
{
MoveBy(glideX, glideY);
}
}
// Reset gesture state
gestureCommitted = false;
totalDeltaX = totalDeltaY = 0;
return;
}
isGliding = false;
// Smooth input deltas
double smoothedX = SmoothDelta(dx, bufX);
double smoothedY = SmoothDelta(dy, bufY);
// Apply acceleration ramp
double rampedX = ApplyAccelerationRamp(smoothedX);
double rampedY = ApplyAccelerationRamp(smoothedY);
// Accumulate gesture until committed
if (!gestureCommitted)
{
totalDeltaX += rampedX;
totalDeltaY += rampedY;
double gestureMagSq = totalDeltaX * totalDeltaX + totalDeltaY * totalDeltaY;
if (gestureMagSq > gestureRadiusSq)
{
gestureCommitted = true;
gestureFlushX = totalDeltaX;
gestureFlushY = totalDeltaY;
velocityHistoryX.Clear();
velocityHistoryY.Clear();
velocitySumX = velocitySumY = 0;
}
else return;
}
// Track velocity using rolling sum
velocityHistoryX.Enqueue(rampedX);
velocitySumX += rampedX;
if (velocityHistoryX.Count > velocityWindowSize)
velocitySumX -= velocityHistoryX.Dequeue();
velocityHistoryY.Enqueue(rampedY);
velocitySumY += rampedY;
if (velocityHistoryY.Count > velocityWindowSize)
velocitySumY -= velocityHistoryY.Dequeue();
releaseVelocityX = velocitySumX / velocityHistoryX.Count;
releaseVelocityY = velocitySumY / velocityHistoryY.Count;
// Apply gesture flush
double flushedX = gestureFlushX * gestureFlushRate;
double flushedY = gestureFlushY * gestureFlushRate;
gestureFlushX -= flushedX;
gestureFlushY -= flushedY;
// Final movement vector
double finalX = flushedX + rampedX;
double finalY = flushedY + rampedY;
MoveBy(finalX, finalY);
}
// Checks whether the pads have been dragged enough to trigger a haptic feedback
public bool HapticDragRFauxLizard(double dx, double dy, bool isTouched)
{
if (isTouched)
{
double finalMagSq = dx * dx + dy * dy;
if (hapticDeltaR < hapticTriggerDelta)
hapticDeltaR += Math.Sqrt(finalMagSq);
if (hapticDeltaR >= hapticTriggerDelta)
{
hapticDeltaR -= hapticTriggerDelta;
return true;
}
}
else
hapticDeltaR = 0;
return false;
}
public bool HapticDragLFauxLizard(double dx, double dy, bool isTouched)
{
if (isTouched)
{
double finalMagSq = dx * dx + dy * dy;
if (hapticDeltaL < hapticTriggerDelta)
hapticDeltaL += Math.Sqrt(finalMagSq);
if (hapticDeltaL >= hapticTriggerDelta)
{
hapticDeltaL -= hapticTriggerDelta;
return true;
}
}
else
hapticDeltaL = 0;
return false;
}
// Input smoothing
private double SmoothDelta(double raw, Queue<double> buffer)
{
buffer.Enqueue(raw);
if (buffer.Count > bufferSize) buffer.Dequeue();
double sum = 0;
foreach (var v in buffer) sum += v;
return sum / buffer.Count;
}
// Acceleration ramp
private double ApplyAccelerationRamp(double delta)
{
double steepness = 0.1;
double midpoint = 6.0;
double maxBoost = 2.0;
double baseBoost = 1.7;
double absDelta = Math.Abs(delta);
double sigmoidBoost = 1.0 + (maxBoost - 1.0) / (1.0 + Math.Exp(-steepness * (absDelta - midpoint)));
return delta * Math.Max(sigmoidBoost, baseBoost);
}
// Glide decay
private double ApplyReleaseGlide(double velocity, double elapsed)
{
double rampedRate = 3.0 + Math.Pow(elapsed * 7.0, 2);
return velocity * Math.Exp(-elapsed * rampedRate);
}
}
}