mirror of
https://github.com/ayufan/steam-deck-tools.git
synced 2026-02-11 10:04:23 +01:00
FLM: Code tidy up to match ayufan's style, and reduce repetition.
This commit is contained in:
parent
0ab15fb6ac
commit
abbe6094f3
|
|
@ -1,506 +0,0 @@
|
|||
using System;
|
||||
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 int hapticBufferSize = 10; // Haptic smoothing and jitter reduction buffer
|
||||
private const double hapticTriggerDelta = 30; // Distance required for haptic feedback on each pad
|
||||
private const int hapticResetTime = 5; // Number of seconds to invalidate a haptic feedback, prevents triggering on drag jitter
|
||||
|
||||
// Functional constants (derived once)
|
||||
private readonly double gestureRadiusSq = gestureRadius * gestureRadius;
|
||||
private readonly double minGlideMagnitudeSq = minGlideMagnitude * minGlideMagnitude;
|
||||
private readonly double minGlideVelocitySq = minGlideVelocity * minGlideVelocity;
|
||||
private readonly double hapticBufferMidpoint = hapticBufferSize / 2.0;
|
||||
|
||||
// Runtime state RPad
|
||||
private readonly double[] bufX = new double[bufferSize];
|
||||
private int bufXIndex = 0, bufXCount = 0;
|
||||
private double bufXSum = 0;
|
||||
private readonly double[] bufY = new double[bufferSize];
|
||||
private int bufYIndex = 0, bufYCount = 0;
|
||||
private double bufYSum = 0;
|
||||
private double totalDeltaX = 0, totalDeltaY = 0;
|
||||
private bool gestureCommitted = false;
|
||||
private double gestureFlushX = 0, gestureFlushY = 0;
|
||||
|
||||
private bool isGliding = false;
|
||||
private long releaseTicks;
|
||||
private double releaseVelocityX = 0, releaseVelocityY = 0;
|
||||
private readonly double[] velocityX = new double[velocityWindowSize];
|
||||
private int velocityXIndex = 0, velocityXCount = 0;
|
||||
private double velocityXSum = 0;
|
||||
private readonly double[] velocityY = new double[velocityWindowSize];
|
||||
private int velocityYIndex = 0, velocityYCount = 0;
|
||||
private double velocityYSum = 0;
|
||||
|
||||
// Runtime state haptics
|
||||
// Right track pad
|
||||
private readonly int[] hapticDxSignBufferR = new int[hapticBufferSize];
|
||||
private readonly int[] hapticDySignBufferR = new int[hapticBufferSize];
|
||||
private readonly int[] hapticDxFlipFlagBufferR = new int[hapticBufferSize];
|
||||
private readonly int[] hapticDyFlipFlagBufferR = new int[hapticBufferSize];
|
||||
private readonly double[] hapticMagBufferR = new double[hapticBufferSize];
|
||||
|
||||
private double hapticDeltaR = 0;
|
||||
private int hapticBufferCountR = 0;
|
||||
private int hapticBufferIndexR = 0;
|
||||
private int hapticDxFlipCountR = 0;
|
||||
private int hapticDyFlipCountR = 0;
|
||||
private int hapticDxZeroCountR = 0;
|
||||
private int hapticDyZeroCountR = 0;
|
||||
private double hapticMagSumR = 0;
|
||||
private DateTime hapticLastTimeR = DateTime.MinValue;
|
||||
|
||||
private bool hapticClearedR = true;
|
||||
|
||||
// Left track pad
|
||||
private readonly int[] hapticDxSignBufferL = new int[hapticBufferSize];
|
||||
private readonly int[] hapticDySignBufferL = new int[hapticBufferSize];
|
||||
private readonly int[] hapticDxFlipFlagBufferL = new int[hapticBufferSize];
|
||||
private readonly int[] hapticDyFlipFlagBufferL = new int[hapticBufferSize];
|
||||
private readonly double[] hapticMagBufferL = new double[hapticBufferSize];
|
||||
|
||||
private double hapticDeltaL = 0;
|
||||
private int hapticBufferCountL = 0;
|
||||
private int hapticBufferIndexL = 0;
|
||||
private int hapticDxFlipCountL = 0;
|
||||
private int hapticDyFlipCountL = 0;
|
||||
private int hapticDxZeroCountL = 0;
|
||||
private int hapticDyZeroCountL = 0;
|
||||
private double hapticMagSumL = 0;
|
||||
private DateTime hapticLastTimeL = DateTime.MinValue;
|
||||
|
||||
private bool hapticClearedL = true;
|
||||
|
||||
// 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)
|
||||
{
|
||||
releaseTicks = System.Diagnostics.Stopwatch.GetTimestamp();
|
||||
isGliding = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Continue glide if active
|
||||
if (isGliding)
|
||||
{
|
||||
double elapsed =
|
||||
(System.Diagnostics.Stopwatch.GetTimestamp() - releaseTicks)
|
||||
/ (double)System.Diagnostics.Stopwatch.Frequency;
|
||||
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;
|
||||
bufXIndex = bufXCount = bufYIndex = bufYCount = velocityXIndex = velocityXCount = velocityYIndex = velocityYCount = 0;
|
||||
bufYSum = bufXSum = velocityXSum = velocityYSum = totalDeltaX = totalDeltaY = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
isGliding = false;
|
||||
|
||||
// Smooth input deltas
|
||||
double smoothedX = SmoothDelta(dx, bufX, ref bufXIndex, ref bufXCount, ref bufXSum);
|
||||
double smoothedY = SmoothDelta(dy, bufY, ref bufYIndex, ref bufYCount, ref bufYSum);
|
||||
|
||||
// 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;
|
||||
|
||||
velocityXIndex = velocityXCount = 0; velocityXSum = 0;
|
||||
velocityYIndex = velocityYCount = 0; velocityYSum = 0;
|
||||
}
|
||||
else return;
|
||||
}
|
||||
|
||||
// Track velocity using rolling sum
|
||||
releaseVelocityX = SmoothVelocity(rampedX, velocityX, ref velocityXIndex, ref velocityXCount, ref velocityXSum);
|
||||
releaseVelocityY = SmoothVelocity(rampedY, velocityY, ref velocityYIndex, ref velocityYCount, ref velocityYSum);
|
||||
|
||||
// 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)
|
||||
{
|
||||
hapticClearedR = false;
|
||||
|
||||
// Timeout check: reset delta if accumulation is too slow
|
||||
DateTime now = DateTime.UtcNow;
|
||||
if (hapticBufferCountR == 0)
|
||||
{
|
||||
hapticLastTimeR = now;
|
||||
}
|
||||
else if ((now - hapticLastTimeR).TotalSeconds > hapticResetTime)
|
||||
{
|
||||
hapticDeltaR = 0;
|
||||
hapticLastTimeR = now;
|
||||
}
|
||||
|
||||
// If buffer is full, remove array contributions at this slot
|
||||
if (hapticBufferCountR == hapticBufferSize)
|
||||
{
|
||||
int idx = hapticBufferIndexR;
|
||||
hapticDxFlipCountR -= hapticDxFlipFlagBufferR[idx];
|
||||
hapticDyFlipCountR -= hapticDyFlipFlagBufferR[idx];
|
||||
if (hapticDxSignBufferR[idx] == 0) hapticDxZeroCountR--;
|
||||
if (hapticDySignBufferR[idx] == 0) hapticDyZeroCountR--;
|
||||
hapticMagSumR -= hapticMagBufferR[idx];
|
||||
}
|
||||
else
|
||||
{
|
||||
hapticBufferCountR++;
|
||||
}
|
||||
|
||||
// Compute current signs
|
||||
int curDxSign = Math.Sign(dx);
|
||||
int curDySign = Math.Sign(dy);
|
||||
|
||||
// Compute flips against previous signs
|
||||
int dxFlip = 0, dyFlip = 0;
|
||||
if (hapticBufferCountR > 1)
|
||||
{
|
||||
int prevIndex = (hapticBufferIndexR - 1 + hapticBufferSize) % hapticBufferSize;
|
||||
int prevDxSign = hapticDxSignBufferR[prevIndex];
|
||||
int prevDySign = hapticDySignBufferR[prevIndex];
|
||||
|
||||
if (prevDxSign != 0 && curDxSign != 0 && prevDxSign != curDxSign) dxFlip = 1;
|
||||
if (prevDySign != 0 && curDySign != 0 && prevDySign != curDySign) dyFlip = 1;
|
||||
}
|
||||
|
||||
// Compute current magnitude
|
||||
double mag = Math.Sqrt(dx * dx + dy * dy);
|
||||
|
||||
// Store signs and flip flags and magnitude in array for rolling sums
|
||||
int i = hapticBufferIndexR;
|
||||
hapticDxSignBufferR[i] = curDxSign;
|
||||
hapticDySignBufferR[i] = curDySign;
|
||||
hapticDxFlipFlagBufferR[i] = dxFlip;
|
||||
hapticDyFlipFlagBufferR[i] = dyFlip;
|
||||
hapticMagBufferR[i] = mag;
|
||||
|
||||
// Increment rolling sums
|
||||
if (curDxSign == 0) hapticDxZeroCountR++;
|
||||
if (curDySign == 0) hapticDyZeroCountR++;
|
||||
hapticDxFlipCountR += dxFlip;
|
||||
hapticDyFlipCountR += dyFlip;
|
||||
hapticMagSumR += mag;
|
||||
|
||||
// Store new buffer index for later
|
||||
hapticBufferIndexR++;
|
||||
if (hapticBufferIndexR == hapticBufferSize) hapticBufferIndexR = 0;
|
||||
|
||||
// No need to proceed as buffer is not full yet
|
||||
if (hapticBufferCountR != hapticBufferSize)
|
||||
return false;
|
||||
|
||||
// Compute average magnitude over buffer for smoothing
|
||||
double avgMag = hapticMagSumR / hapticBufferCountR;
|
||||
|
||||
// Calculate a penalty for the magnitude
|
||||
double bufferSpan = hapticBufferSize - hapticBufferMidpoint;
|
||||
double invSpan = 1.0 / bufferSpan;
|
||||
bool dxZeroEdge = hapticDxZeroCountR == hapticBufferCountR;
|
||||
bool dyZeroEdge = hapticDyZeroCountR == hapticBufferCountR;
|
||||
double factor = (dxZeroEdge || dyZeroEdge) ? 1.0 : 0.5;
|
||||
|
||||
double dxFactor = dxZeroEdge
|
||||
? 0.0
|
||||
: (hapticDxFlipCountR < hapticBufferMidpoint
|
||||
? factor
|
||||
: -factor * ((hapticDxFlipCountR - hapticBufferMidpoint) * invSpan));
|
||||
|
||||
double dyFactor = dyZeroEdge
|
||||
? 0.0
|
||||
: (hapticDyFlipCountR < hapticBufferMidpoint
|
||||
? factor
|
||||
: -factor * ((hapticDyFlipCountR - hapticBufferMidpoint) * invSpan));
|
||||
|
||||
// Final penalty
|
||||
hapticDeltaR += avgMag * (dxFactor + dyFactor);
|
||||
|
||||
if (hapticDeltaR >= hapticTriggerDelta)
|
||||
{
|
||||
hapticDeltaR -= Math.Floor(hapticDeltaR / hapticTriggerDelta) * hapticTriggerDelta;
|
||||
hapticLastTimeR = now;
|
||||
return true;
|
||||
}
|
||||
else if (hapticDeltaR < 0)
|
||||
{
|
||||
hapticDeltaR = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!hapticClearedR)
|
||||
{
|
||||
hapticClearedR = true;
|
||||
|
||||
// Reset all buffers when not touched
|
||||
hapticDeltaR = 0;
|
||||
hapticBufferCountR = 0;
|
||||
hapticBufferIndexR = 0;
|
||||
hapticDxFlipCountR = 0;
|
||||
hapticDyFlipCountR = 0;
|
||||
hapticDxZeroCountR = 0;
|
||||
hapticDyZeroCountR = 0;
|
||||
hapticMagSumR = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool HapticDragLFauxLizard(double dx, double dy, bool isTouched)
|
||||
{
|
||||
if (isTouched)
|
||||
{
|
||||
hapticClearedL = false;
|
||||
|
||||
// Timeout check: reset delta if accumulation is too slow
|
||||
DateTime now = DateTime.UtcNow;
|
||||
if (hapticBufferCountL == 0)
|
||||
{
|
||||
hapticLastTimeL = now;
|
||||
}
|
||||
else if ((now - hapticLastTimeL).TotalSeconds > hapticResetTime)
|
||||
{
|
||||
hapticDeltaL = 0;
|
||||
hapticLastTimeL = now;
|
||||
}
|
||||
|
||||
// If buffer is full, remove array contributions at this slot
|
||||
if (hapticBufferCountL == hapticBufferSize)
|
||||
{
|
||||
int idx = hapticBufferIndexL;
|
||||
hapticDxFlipCountL -= hapticDxFlipFlagBufferL[idx];
|
||||
hapticDyFlipCountL -= hapticDyFlipFlagBufferL[idx];
|
||||
if (hapticDxSignBufferL[idx] == 0) hapticDxZeroCountL--;
|
||||
if (hapticDySignBufferL[idx] == 0) hapticDyZeroCountL--;
|
||||
hapticMagSumL -= hapticMagBufferL[idx];
|
||||
}
|
||||
else
|
||||
{
|
||||
hapticBufferCountL++;
|
||||
}
|
||||
|
||||
// Compute current signs
|
||||
int curDxSign = Math.Sign(dx);
|
||||
int curDySign = Math.Sign(dy);
|
||||
|
||||
// Compute flips against previous signs
|
||||
int dxFlip = 0, dyFlip = 0;
|
||||
if (hapticBufferCountL > 1)
|
||||
{
|
||||
int prevIndex = (hapticBufferIndexL - 1 + hapticBufferSize) % hapticBufferSize;
|
||||
int prevDxSign = hapticDxSignBufferL[prevIndex];
|
||||
int prevDySign = hapticDySignBufferL[prevIndex];
|
||||
|
||||
if (prevDxSign != 0 && curDxSign != 0 && prevDxSign != curDxSign) dxFlip = 1;
|
||||
if (prevDySign != 0 && curDySign != 0 && prevDySign != curDySign) dyFlip = 1;
|
||||
}
|
||||
|
||||
// Compute current magnitude
|
||||
double mag = Math.Sqrt(dx * dx + dy * dy);
|
||||
|
||||
// Store signs and flip flags and magnitude in array for rolling sums
|
||||
int i = hapticBufferIndexL;
|
||||
hapticDxSignBufferL[i] = curDxSign;
|
||||
hapticDySignBufferL[i] = curDySign;
|
||||
hapticDxFlipFlagBufferL[i] = dxFlip;
|
||||
hapticDyFlipFlagBufferL[i] = dyFlip;
|
||||
hapticMagBufferL[i] = mag;
|
||||
|
||||
// Increment rolling sums
|
||||
if (curDxSign == 0) hapticDxZeroCountL++;
|
||||
if (curDySign == 0) hapticDyZeroCountL++;
|
||||
hapticDxFlipCountL += dxFlip;
|
||||
hapticDyFlipCountL += dyFlip;
|
||||
hapticMagSumL += mag;
|
||||
|
||||
// Store new buffer index for later
|
||||
hapticBufferIndexL++;
|
||||
if (hapticBufferIndexL == hapticBufferSize) hapticBufferIndexL = 0;
|
||||
|
||||
// No need to proceed as buffer is not full yet
|
||||
if (hapticBufferCountL != hapticBufferSize)
|
||||
return false;
|
||||
|
||||
// Compute average magnitude over buffer for smoothing
|
||||
double avgMag = hapticMagSumL / hapticBufferCountL;
|
||||
|
||||
// Calculate a penalty for the magnitude
|
||||
double bufferSpan = hapticBufferSize - hapticBufferMidpoint;
|
||||
double invSpan = 1.0 / bufferSpan;
|
||||
bool dxZeroEdge = hapticDxZeroCountL == hapticBufferCountL;
|
||||
bool dyZeroEdge = hapticDyZeroCountL == hapticBufferCountL;
|
||||
double factor = (dxZeroEdge || dyZeroEdge) ? 1.0 : 0.5;
|
||||
|
||||
double dxFactor = dxZeroEdge
|
||||
? 0.0
|
||||
: (hapticDxFlipCountL < hapticBufferMidpoint
|
||||
? factor
|
||||
: -factor * ((hapticDxFlipCountL - hapticBufferMidpoint) * invSpan));
|
||||
|
||||
double dyFactor = dyZeroEdge
|
||||
? 0.0
|
||||
: (hapticDyFlipCountL < hapticBufferMidpoint
|
||||
? factor
|
||||
: -factor * ((hapticDyFlipCountL - hapticBufferMidpoint) * invSpan));
|
||||
|
||||
// Final penalty
|
||||
hapticDeltaL += avgMag * (dxFactor + dyFactor);
|
||||
|
||||
if (hapticDeltaL >= hapticTriggerDelta)
|
||||
{
|
||||
hapticDeltaL -= Math.Floor(hapticDeltaL / hapticTriggerDelta) * hapticTriggerDelta;
|
||||
hapticLastTimeL = now;
|
||||
return true;
|
||||
}
|
||||
else if (hapticDeltaL < 0)
|
||||
{
|
||||
hapticDeltaL = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!hapticClearedL)
|
||||
{
|
||||
hapticClearedL = true;
|
||||
|
||||
// Reset all buffers when not touched
|
||||
hapticDeltaL = 0;
|
||||
hapticBufferCountL = 0;
|
||||
hapticBufferIndexL = 0;
|
||||
hapticDxFlipCountL = 0;
|
||||
hapticDyFlipCountL = 0;
|
||||
hapticDxZeroCountL = 0;
|
||||
hapticDyZeroCountL = 0;
|
||||
hapticMagSumL = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Input smoothing
|
||||
private double SmoothDelta(double raw, double[] buffer, ref int index, ref int count, ref double sum)
|
||||
{
|
||||
if (count < bufferSize)
|
||||
{
|
||||
buffer[index] = raw;
|
||||
sum += raw;
|
||||
count++;
|
||||
}
|
||||
else
|
||||
{
|
||||
sum -= buffer[index];
|
||||
buffer[index] = raw;
|
||||
sum += raw;
|
||||
}
|
||||
|
||||
index++;
|
||||
if (index == bufferSize)
|
||||
index = 0;
|
||||
|
||||
return sum / count;
|
||||
}
|
||||
|
||||
// Velocity smoothing
|
||||
private double SmoothVelocity(double v, double[] buffer, ref int index, ref int count, ref double sum)
|
||||
{
|
||||
if (count < velocityWindowSize)
|
||||
{
|
||||
buffer[index] = v;
|
||||
sum += v;
|
||||
count++;
|
||||
}
|
||||
else
|
||||
{
|
||||
sum -= buffer[index];
|
||||
buffer[index] = v;
|
||||
sum += v;
|
||||
}
|
||||
|
||||
index++;
|
||||
if (index == velocityWindowSize)
|
||||
index = 0;
|
||||
|
||||
return sum / 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
199
SteamController/Devices/MouseControllerFauxLizardHaptic.cs
Normal file
199
SteamController/Devices/MouseControllerFauxLizardHaptic.cs
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
namespace SteamController.Devices
|
||||
{
|
||||
public partial class MouseController
|
||||
{
|
||||
// Adjustable parameters (tweakable settings)
|
||||
private const int HapticBufferSize = 10; // Haptic smoothing and jitter reduction buffer
|
||||
private const double HapticTriggerDelta = 30; // Distance required for haptic feedback on each pad
|
||||
private const int HapticResetTime = 5; // Number of seconds to invalidate a haptic feedback, prevents triggering on drag jitter
|
||||
|
||||
// Functional constants (derived once)
|
||||
private static readonly double HapticBufferMidpoint = HapticBufferSize / 2.0;
|
||||
|
||||
// Runtime state haptics
|
||||
private struct HapticState
|
||||
{
|
||||
public readonly int[] DxSign;
|
||||
public readonly int[] DySign;
|
||||
public readonly int[] DxFlipFlag;
|
||||
public readonly int[] DyFlipFlag;
|
||||
public readonly double[] Mag;
|
||||
|
||||
public double Delta;
|
||||
public int BufferCount;
|
||||
public int BufferIndex;
|
||||
public int DxFlipCount;
|
||||
public int DyFlipCount;
|
||||
public int DxZeroCount;
|
||||
public int DyZeroCount;
|
||||
public double MagSum;
|
||||
public DateTime LastTime;
|
||||
public bool Cleared;
|
||||
|
||||
public HapticState(int n)
|
||||
{
|
||||
DxSign = new int[n];
|
||||
DySign = new int[n];
|
||||
DxFlipFlag = new int[n];
|
||||
DyFlipFlag = new int[n];
|
||||
Mag = new double[n];
|
||||
|
||||
Delta = 0;
|
||||
BufferCount = 0;
|
||||
BufferIndex = 0;
|
||||
DxFlipCount = 0;
|
||||
DyFlipCount = 0;
|
||||
DxZeroCount = 0;
|
||||
DyZeroCount = 0;
|
||||
MagSum = 0;
|
||||
LastTime = DateTime.MinValue;
|
||||
Cleared = true;
|
||||
}
|
||||
|
||||
public void ResetBuffers()
|
||||
{
|
||||
Delta = 0;
|
||||
BufferCount = 0;
|
||||
BufferIndex = 0;
|
||||
DxFlipCount = 0;
|
||||
DyFlipCount = 0;
|
||||
DxZeroCount = 0;
|
||||
DyZeroCount = 0;
|
||||
MagSum = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private HapticState hapticR = new HapticState(HapticBufferSize);
|
||||
private HapticState hapticL = new HapticState(HapticBufferSize);
|
||||
|
||||
// Checks whether the pads have been dragged enough to trigger a haptic feedback
|
||||
public bool HapticDragRFauxLizard(double dx, double dy, bool isTouched)
|
||||
=> HapticDragFauxLizard(ref hapticR, dx, dy, isTouched);
|
||||
|
||||
public bool HapticDragLFauxLizard(double dx, double dy, bool isTouched)
|
||||
=> HapticDragFauxLizard(ref hapticL, dx, dy, isTouched);
|
||||
|
||||
private bool HapticDragFauxLizard(ref HapticState s, double dx, double dy, bool isTouched)
|
||||
{
|
||||
if (isTouched)
|
||||
{
|
||||
s.Cleared = false;
|
||||
|
||||
// Timeout check: reset delta if accumulation is too slow
|
||||
DateTime now = DateTime.UtcNow;
|
||||
if (s.BufferCount == 0)
|
||||
{
|
||||
s.LastTime = now;
|
||||
}
|
||||
else if ((now - s.LastTime).TotalSeconds > HapticResetTime)
|
||||
{
|
||||
s.Delta = 0;
|
||||
s.LastTime = now;
|
||||
}
|
||||
|
||||
// If buffer is full, remove array contributions at this slot
|
||||
if (s.BufferCount == HapticBufferSize)
|
||||
{
|
||||
int idx = s.BufferIndex;
|
||||
s.DxFlipCount -= s.DxFlipFlag[idx];
|
||||
s.DyFlipCount -= s.DyFlipFlag[idx];
|
||||
if (s.DxSign[idx] == 0) s.DxZeroCount--;
|
||||
if (s.DySign[idx] == 0) s.DyZeroCount--;
|
||||
s.MagSum -= s.Mag[idx];
|
||||
}
|
||||
else
|
||||
{
|
||||
s.BufferCount++;
|
||||
}
|
||||
|
||||
// Compute current signs
|
||||
int curDxSign = Math.Sign(dx);
|
||||
int curDySign = Math.Sign(dy);
|
||||
|
||||
// Compute flips against previous signs
|
||||
int dxFlip = 0, dyFlip = 0;
|
||||
if (s.BufferCount > 1)
|
||||
{
|
||||
int prevIndex = (s.BufferIndex - 1 + HapticBufferSize) % HapticBufferSize;
|
||||
int prevDxSign = s.DxSign[prevIndex];
|
||||
int prevDySign = s.DySign[prevIndex];
|
||||
|
||||
if (prevDxSign != 0 && curDxSign != 0 && prevDxSign != curDxSign) dxFlip = 1;
|
||||
if (prevDySign != 0 && curDySign != 0 && prevDySign != curDySign) dyFlip = 1;
|
||||
}
|
||||
|
||||
// Compute current magnitude
|
||||
double mag = Math.Sqrt(dx * dx + dy * dy);
|
||||
|
||||
// Store signs and flip flags and magnitude in array for rolling sums
|
||||
int i = s.BufferIndex;
|
||||
s.DxSign[i] = curDxSign;
|
||||
s.DySign[i] = curDySign;
|
||||
s.DxFlipFlag[i] = dxFlip;
|
||||
s.DyFlipFlag[i] = dyFlip;
|
||||
s.Mag[i] = mag;
|
||||
|
||||
// Increment rolling sums
|
||||
if (curDxSign == 0) s.DxZeroCount++;
|
||||
if (curDySign == 0) s.DyZeroCount++;
|
||||
s.DxFlipCount += dxFlip;
|
||||
s.DyFlipCount += dyFlip;
|
||||
s.MagSum += mag;
|
||||
|
||||
// Store new buffer index for later
|
||||
s.BufferIndex++;
|
||||
if (s.BufferIndex == HapticBufferSize) s.BufferIndex = 0;
|
||||
|
||||
// No need to proceed as buffer is not full yet
|
||||
if (s.BufferCount != HapticBufferSize)
|
||||
return false;
|
||||
|
||||
// Compute average magnitude over buffer for smoothing
|
||||
double avgMag = s.MagSum / s.BufferCount;
|
||||
|
||||
// Calculate a penalty for the magnitude
|
||||
double bufferSpan = HapticBufferSize - HapticBufferMidpoint;
|
||||
double invSpan = 1.0 / bufferSpan;
|
||||
bool dxZeroEdge = s.DxZeroCount == s.BufferCount;
|
||||
bool dyZeroEdge = s.DyZeroCount == s.BufferCount;
|
||||
double factor = (dxZeroEdge || dyZeroEdge) ? 1.0 : 0.5;
|
||||
|
||||
double dxFactor = dxZeroEdge
|
||||
? 0.0
|
||||
: (s.DxFlipCount < HapticBufferMidpoint
|
||||
? factor
|
||||
: -factor * ((s.DxFlipCount - HapticBufferMidpoint) * invSpan));
|
||||
|
||||
double dyFactor = dyZeroEdge
|
||||
? 0.0
|
||||
: (s.DyFlipCount < HapticBufferMidpoint
|
||||
? factor
|
||||
: -factor * ((s.DyFlipCount - HapticBufferMidpoint) * invSpan));
|
||||
|
||||
// Final penalty
|
||||
s.Delta += avgMag * (dxFactor + dyFactor);
|
||||
|
||||
if (s.Delta >= HapticTriggerDelta)
|
||||
{
|
||||
s.Delta -= Math.Floor(s.Delta / HapticTriggerDelta) * HapticTriggerDelta;
|
||||
s.LastTime = now;
|
||||
return true;
|
||||
}
|
||||
else if (s.Delta < 0)
|
||||
{
|
||||
s.Delta = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!s.Cleared)
|
||||
{
|
||||
s.Cleared = true;
|
||||
s.ResetBuffers();
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
198
SteamController/Devices/MouseControllerFauxLizardMove.cs
Normal file
198
SteamController/Devices/MouseControllerFauxLizardMove.cs
Normal file
|
|
@ -0,0 +1,198 @@
|
|||
namespace SteamController.Devices
|
||||
{
|
||||
public partial class MouseController
|
||||
{
|
||||
// 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
|
||||
|
||||
// Functional constants (derived once)
|
||||
private static readonly double GestureRadiusSq = GestureRadius * GestureRadius;
|
||||
private static readonly double MinGlideMagnitudeSq = MinGlideMagnitude * MinGlideMagnitude;
|
||||
private static readonly double MinGlideVelocitySq = MinGlideVelocity * MinGlideVelocity;
|
||||
|
||||
// Runtime state RPad (movement)
|
||||
private readonly double[] bufX = new double[BufferSize];
|
||||
private int bufXIndex = 0, bufXCount = 0;
|
||||
private double bufXSum = 0;
|
||||
|
||||
private readonly double[] bufY = new double[BufferSize];
|
||||
private int bufYIndex = 0, bufYCount = 0;
|
||||
private double bufYSum = 0;
|
||||
|
||||
private double totalDeltaX = 0, totalDeltaY = 0;
|
||||
private bool gestureCommitted = false;
|
||||
private double gestureFlushX = 0, gestureFlushY = 0;
|
||||
|
||||
private bool isGliding = false;
|
||||
private bool wasTouched = false;
|
||||
private long releaseTicks;
|
||||
private double releaseVelocityX = 0, releaseVelocityY = 0;
|
||||
|
||||
private readonly double[] velocityX = new double[VelocityWindowSize];
|
||||
private int velocityXIndex = 0, velocityXCount = 0;
|
||||
private double velocityXSum = 0;
|
||||
|
||||
private readonly double[] velocityY = new double[VelocityWindowSize];
|
||||
private int velocityYIndex = 0, velocityYCount = 0;
|
||||
private double velocityYSum = 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)
|
||||
{
|
||||
releaseTicks = System.Diagnostics.Stopwatch.GetTimestamp();
|
||||
isGliding = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Continue glide if active
|
||||
if (isGliding)
|
||||
{
|
||||
double elapsed =
|
||||
(System.Diagnostics.Stopwatch.GetTimestamp() - releaseTicks)
|
||||
/ (double)System.Diagnostics.Stopwatch.Frequency;
|
||||
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 ONCE on touch release
|
||||
if (wasTouched)
|
||||
{
|
||||
ResetFauxLizardGesture();
|
||||
wasTouched = false;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Touch just started
|
||||
wasTouched = true;
|
||||
isGliding = false;
|
||||
|
||||
// Smooth input deltas
|
||||
double smoothedX = SmoothRing(dx, bufX, ref bufXIndex, ref bufXCount, ref bufXSum);
|
||||
double smoothedY = SmoothRing(dy, bufY, ref bufYIndex, ref bufYCount, ref bufYSum);
|
||||
|
||||
// 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;
|
||||
|
||||
velocityXIndex = velocityXCount = 0; velocityXSum = 0;
|
||||
velocityYIndex = velocityYCount = 0; velocityYSum = 0;
|
||||
}
|
||||
else return;
|
||||
}
|
||||
|
||||
// Track velocity using rolling sum
|
||||
releaseVelocityX = SmoothRing(rampedX, velocityX, ref velocityXIndex, ref velocityXCount, ref velocityXSum);
|
||||
releaseVelocityY = SmoothRing(rampedY, velocityY, ref velocityYIndex, ref velocityYCount, ref velocityYSum);
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
// Buffer smoothing helper
|
||||
private static double SmoothRing(double v, double[] buffer, ref int index, ref int count, ref double sum)
|
||||
{
|
||||
int window = buffer.Length;
|
||||
|
||||
if (count < window)
|
||||
{
|
||||
buffer[index] = v;
|
||||
sum += v;
|
||||
count++;
|
||||
}
|
||||
else
|
||||
{
|
||||
sum -= buffer[index];
|
||||
buffer[index] = v;
|
||||
sum += v;
|
||||
}
|
||||
|
||||
index++;
|
||||
if (index == window)
|
||||
index = 0;
|
||||
|
||||
return sum / 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);
|
||||
}
|
||||
|
||||
//Resets all values for a new touch
|
||||
private void ResetFauxLizardGesture()
|
||||
{
|
||||
gestureCommitted = false;
|
||||
|
||||
bufXIndex = bufXCount = 0; bufXSum = 0;
|
||||
bufYIndex = bufYCount = 0; bufYSum = 0;
|
||||
|
||||
velocityXIndex = velocityXCount = 0; velocityXSum = 0;
|
||||
velocityYIndex = velocityYCount = 0; velocityYSum = 0;
|
||||
|
||||
totalDeltaX = totalDeltaY = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue