Skip to content

Commit 4805174

Browse files
committed
Convert OpenXREyeGazeDataProvider to EyeGazeSmoother
1 parent 666b0d4 commit 4805174

File tree

1 file changed

+32
-148
lines changed

1 file changed

+32
-148
lines changed

Assets/MRTK/Providers/OpenXR/Scripts/OpenXREyeGazeDataProvider.cs

Lines changed: 32 additions & 148 deletions
Original file line numberDiff line numberDiff line change
@@ -34,31 +34,37 @@ public OpenXREyeGazeDataProvider(
3434
IMixedRealityInputSystem inputSystem,
3535
string name,
3636
uint priority,
37-
BaseMixedRealityProfile profile) : base(inputSystem, name, priority, profile) { }
37+
BaseMixedRealityProfile profile) : base(inputSystem, name, priority, profile)
38+
{
39+
gazeSmoother = new EyeGazeSmoother();
40+
41+
// Register for these events to forward along, in case code is still registering for the obsolete actions
42+
gazeSmoother.OnSaccade += GazeSmoother_OnSaccade;
43+
gazeSmoother.OnSaccadeX += GazeSmoother_OnSaccadeX;
44+
gazeSmoother.OnSaccadeY += GazeSmoother_OnSaccadeY;
45+
}
3846

3947
/// <inheritdoc />
4048
public bool SmoothEyeTracking { get; set; } = false;
4149

4250
/// <inheritdoc />
43-
public IMixedRealityEyeSaccadeProvider SaccadeProvider => this;
51+
public IMixedRealityEyeSaccadeProvider SaccadeProvider => gazeSmoother;
52+
private readonly EyeGazeSmoother gazeSmoother;
4453

4554
/// <inheritdoc />
55+
[Obsolete("Register for this provider's SaccadeProvider's actions instead")]
4656
public event Action OnSaccade;
57+
private void GazeSmoother_OnSaccade() => OnSaccade?.Invoke();
4758

4859
/// <inheritdoc />
60+
[Obsolete("Register for this provider's SaccadeProvider's actions instead")]
4961
public event Action OnSaccadeX;
62+
private void GazeSmoother_OnSaccadeX() => OnSaccadeX?.Invoke();
5063

5164
/// <inheritdoc />
65+
[Obsolete("Register for this provider's SaccadeProvider's actions instead")]
5266
public event Action OnSaccadeY;
53-
54-
private readonly float smoothFactorNormalized = 0.96f;
55-
private readonly float saccadeThreshInDegree = 2.5f; // in degrees (not radians)
56-
57-
private Ray? oldGaze;
58-
private int confidenceOfSaccade = 0;
59-
private int confidenceOfSaccadeThreshold = 6; // TODO(https://github.com/Microsoft/MixedRealityToolkit-Unity/issues/3767): This value should be adjusted based on the FPS of the ET system
60-
private Ray saccade_initialGazePoint;
61-
private readonly List<Ray> saccade_newGazeCluster = new List<Ray>();
67+
private void GazeSmoother_OnSaccadeY() => OnSaccadeY?.Invoke();
6268

6369
private static readonly List<InputDevice> InputDeviceList = new List<InputDevice>();
6470
private InputDevice eyeTrackingDevice = default(InputDevice);
@@ -85,14 +91,14 @@ private void ReadProfile()
8591
{
8692
if (ConfigurationProfile == null)
8793
{
88-
Debug.LogError("OpenXR Eye Tracking Provider requires a configuration profile to run properly.");
94+
Debug.LogError($"{Name} requires a configuration profile to run properly.");
8995
return;
9096
}
9197

9298
MixedRealityEyeTrackingProfile profile = ConfigurationProfile as MixedRealityEyeTrackingProfile;
9399
if (profile == null)
94100
{
95-
Debug.LogError("OpenXR Eye Tracking Provider's configuration profile must be a MixedRealityEyeTrackingProfile.");
101+
Debug.LogError($"{Name}'s configuration profile must be a MixedRealityEyeTrackingProfile.");
96102
return;
97103
}
98104

@@ -125,150 +131,28 @@ public override void Update()
125131
}
126132
}
127133

128-
#if UNITY_OPENXR
129-
if (eyeTrackingDevice.TryGetFeatureValue(CommonUsages.isTracked, out bool gazeAvailable))
130-
{
131-
Service?.EyeGazeProvider?.UpdateEyeTrackingStatus(this, gazeAvailable);
132-
133-
if (gazeAvailable &&
134-
eyeTrackingDevice.TryGetFeatureValue(EyeTrackingUsages.gazePosition, out Vector3 eyeGazePosition) &&
135-
eyeTrackingDevice.TryGetFeatureValue(EyeTrackingUsages.gazeRotation, out Quaternion eyeGazeRotation))
136-
{
137-
Vector3 worldPosition = MixedRealityPlayspace.TransformPoint(eyeGazePosition);
138-
Vector3 worldRotation = MixedRealityPlayspace.TransformDirection(eyeGazeRotation * Vector3.forward);
139-
140-
Ray newGaze = new Ray(worldPosition, worldRotation);
141-
142-
if (SmoothEyeTracking)
143-
{
144-
newGaze = SmoothGaze(newGaze);
145-
}
146-
147-
Service?.EyeGazeProvider?.UpdateEyeGaze(this, newGaze, DateTime.UtcNow);
148-
}
149-
}
150-
else
151-
{
152-
Service?.EyeGazeProvider?.UpdateEyeTrackingStatus(this, false);
153-
}
154-
#endif // UNITY_OPENXR
155-
}
156-
}
157-
158-
private static readonly ProfilerMarker SmoothGazePerfMarker = new ProfilerMarker("[MRTK] OpenXREyeGazeDataProvider.SmoothGaze");
159-
160-
/// <summary>
161-
/// Smooths eye gaze by detecting saccades and tracking gaze clusters.
162-
/// </summary>
163-
/// <param name="newGaze">The ray to smooth.</param>
164-
/// <returns>The smoothed ray.</returns>
165-
private Ray SmoothGaze(Ray? newGaze)
166-
{
167-
using (SmoothGazePerfMarker.Auto())
168-
{
169-
if (!oldGaze.HasValue)
170-
{
171-
oldGaze = newGaze;
172-
return newGaze.Value;
173-
}
174-
175-
Ray smoothedGaze = new Ray();
176-
bool isSaccading = false;
134+
Service?.EyeGazeProvider?.UpdateEyeTrackingStatus(this, true);
177135

178-
// Handle saccades vs. outliers: Instead of simply checking that two successive gaze points are sufficiently
179-
// apart, we check for clusters of gaze points instead.
180-
// 1. If the user's gaze points are far enough apart, this may be a saccade, but also could be an outlier.
181-
// So, let's mark it as a potential saccade.
182-
if ((IsSaccading(oldGaze.Value, newGaze.Value) && (confidenceOfSaccade == 0)))
183-
{
184-
confidenceOfSaccade++;
185-
saccade_initialGazePoint = oldGaze.Value;
186-
saccade_newGazeCluster.Clear();
187-
saccade_newGazeCluster.Add(newGaze.Value);
188-
}
189-
// 2. If we have a potential saccade marked, let's check if the new points are within the proximity of
190-
// the initial saccade point.
191-
else if ((confidenceOfSaccade > 0) && (confidenceOfSaccade < confidenceOfSaccadeThreshold))
192-
{
193-
confidenceOfSaccade++;
194-
195-
// First, let's check that we don't just have a bunch of random outliers
196-
// The assumption is that after a person saccades, they fixate for a certain
197-
// amount of time resulting in a cluster of gaze points.
198-
for (int i = 0; i < saccade_newGazeCluster.Count; i++)
199-
{
200-
if (IsSaccading(saccade_newGazeCluster[i], newGaze.Value))
201-
{
202-
confidenceOfSaccade = 0;
203-
}
204-
205-
// Meanwhile we want to make sure that we are still looking sufficiently far away from our
206-
// original gaze point before saccading.
207-
if (!IsSaccading(saccade_initialGazePoint, newGaze.Value))
208-
{
209-
confidenceOfSaccade = 0;
210-
}
211-
}
212-
saccade_newGazeCluster.Add(newGaze.Value);
213-
}
214-
else if (confidenceOfSaccade == confidenceOfSaccadeThreshold)
215-
{
216-
isSaccading = true;
217-
}
218-
219-
// Saccade-dependent local smoothing
220-
if (isSaccading)
221-
{
222-
smoothedGaze.direction = newGaze.Value.direction;
223-
smoothedGaze.origin = newGaze.Value.origin;
224-
confidenceOfSaccade = 0;
225-
}
226-
else
136+
#if UNITY_OPENXR
137+
if (eyeTrackingDevice.TryGetFeatureValue(CommonUsages.isTracked, out bool gazeTracked)
138+
&& gazeTracked
139+
&& eyeTrackingDevice.TryGetFeatureValue(EyeTrackingUsages.gazePosition, out Vector3 eyeGazePosition)
140+
&& eyeTrackingDevice.TryGetFeatureValue(EyeTrackingUsages.gazeRotation, out Quaternion eyeGazeRotation))
227141
{
228-
smoothedGaze.direction = oldGaze.Value.direction * smoothFactorNormalized + newGaze.Value.direction * (1 - smoothFactorNormalized);
229-
smoothedGaze.origin = oldGaze.Value.origin * smoothFactorNormalized + newGaze.Value.origin * (1 - smoothFactorNormalized);
230-
}
231-
232-
oldGaze = smoothedGaze;
233-
return smoothedGaze;
234-
}
235-
}
236-
237-
private static readonly ProfilerMarker IsSaccadingPerfMarker = new ProfilerMarker("[MRTK] BaseWindowsMixedRealityEyeGazeDataProvider.IsSaccading");
238-
239-
private bool IsSaccading(Ray rayOld, Ray rayNew)
240-
{
241-
using (IsSaccadingPerfMarker.Auto())
242-
{
243-
Vector3 v1 = rayOld.origin + rayOld.direction;
244-
Vector3 v2 = rayNew.origin + rayNew.direction;
142+
Vector3 worldPosition = MixedRealityPlayspace.TransformPoint(eyeGazePosition);
143+
Vector3 worldRotation = MixedRealityPlayspace.TransformDirection(eyeGazeRotation * Vector3.forward);
245144

246-
if (Vector3.Angle(v1, v2) > saccadeThreshInDegree)
247-
{
248-
Vector2 hv1 = new Vector2(v1.x, 0);
249-
Vector2 hv2 = new Vector2(v2.x, 0);
250-
if (Vector2.Angle(hv1, hv2) > saccadeThreshInDegree)
251-
{
252-
PostOnSaccadeHorizontally();
253-
}
145+
Ray newGaze = new Ray(worldPosition, worldRotation);
254146

255-
Vector2 vv1 = new Vector2(0, v1.y);
256-
Vector2 vv2 = new Vector2(0, v2.y);
257-
if (Vector2.Angle(vv1, vv2) > saccadeThreshInDegree)
147+
if (SmoothEyeTracking)
258148
{
259-
PostOnSaccadeVertically();
149+
newGaze = gazeSmoother.SmoothGaze(newGaze);
260150
}
261151

262-
PostOnSaccade();
263-
264-
return true;
152+
Service?.EyeGazeProvider?.UpdateEyeGaze(this, newGaze, DateTime.UtcNow);
265153
}
266-
return false;
154+
#endif // UNITY_OPENXR
267155
}
268156
}
269-
270-
private void PostOnSaccade() => OnSaccade?.Invoke();
271-
private void PostOnSaccadeHorizontally() => OnSaccadeX?.Invoke();
272-
private void PostOnSaccadeVertically() => OnSaccadeY?.Invoke();
273157
}
274158
}

0 commit comments

Comments
 (0)