using System; /// /// One Euro Filter implementation for smoothing eye tracking data /// Based on the paper "1€ Filter" by Géry Casiez et al. /// /// The one euro filter is an adaptive low-pass filter that provides smooth filtering /// while maintaining responsiveness. It smooths more when the signal is stable and /// less when there's rapid movement. /// public class OneEuroFilter { private float _minCutoff; private float _beta; private float _dCutoff; private float _lastTime; private float _lastValue; private float _lastDelta; private bool _firstTime; /// /// Initializes a new instance of the OneEuroFilter class /// /// Minimum cutoff frequency for the low-pass filter (default: 1.0 Hz) /// Speed coefficient for the adaptive cutoff (default: 0.0) /// Cutoff frequency for the derivative low-pass filter (default: 1.0 Hz) public OneEuroFilter(float minCutoff = 1.0f, float beta = 0.0f, float dCutoff = 1.0f) { _minCutoff = minCutoff; _beta = beta; _dCutoff = dCutoff; _firstTime = true; } /// /// Filters a value using the one euro filter algorithm /// /// The input value to filter /// The current time in seconds /// The filtered value public float Filter(float value, float time) { if (_firstTime) { _lastTime = time; _lastValue = value; _lastDelta = 0.0f; _firstTime = false; return value; } // Compute the filtered derivative float delta = (value - _lastValue) / (time - _lastTime); float filteredDelta = Alpha(_dCutoff, time - _lastTime) * delta + (1.0f - Alpha(_dCutoff, time - _lastTime)) * _lastDelta; // Use it to update the speed coefficient float cutoff = _minCutoff + _beta * Math.Abs(filteredDelta); // Filter the value float filteredValue = Alpha(cutoff, time - _lastTime) * value + (1.0f - Alpha(cutoff, time - _lastTime)) * _lastValue; // Update state _lastTime = time; _lastValue = filteredValue; _lastDelta = filteredDelta; return filteredValue; } /// /// Computes the alpha parameter for the low-pass filter /// /// The cutoff frequency /// The time delta /// The alpha value private float Alpha(float cutoff, float deltaTime) { float tau = 1.0f / (2.0f * (float)Math.PI * cutoff); return 1.0f / (1.0f + tau / deltaTime); } /// /// Resets the filter state /// public void Reset() { _firstTime = true; } /// /// Gets or sets the minimum cutoff frequency /// public float MinCutoff { get => _minCutoff; set => _minCutoff = value; } /// /// Gets or sets the speed coefficient /// public float Beta { get => _beta; set => _beta = value; } /// /// Gets or sets the derivative cutoff frequency /// public float DCutoff { get => _dCutoff; set => _dCutoff = value; } }