@@ -34,31 +34,37 @@ public OpenXREyeGazeDataProvider(
34
34
IMixedRealityInputSystem inputSystem ,
35
35
string name ,
36
36
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
+ }
38
46
39
47
/// <inheritdoc />
40
48
public bool SmoothEyeTracking { get ; set ; } = false ;
41
49
42
50
/// <inheritdoc />
43
- public IMixedRealityEyeSaccadeProvider SaccadeProvider => this ;
51
+ public IMixedRealityEyeSaccadeProvider SaccadeProvider => gazeSmoother ;
52
+ private readonly EyeGazeSmoother gazeSmoother ;
44
53
45
54
/// <inheritdoc />
55
+ [ Obsolete ( "Register for this provider's SaccadeProvider's actions instead" ) ]
46
56
public event Action OnSaccade ;
57
+ private void GazeSmoother_OnSaccade ( ) => OnSaccade ? . Invoke ( ) ;
47
58
48
59
/// <inheritdoc />
60
+ [ Obsolete ( "Register for this provider's SaccadeProvider's actions instead" ) ]
49
61
public event Action OnSaccadeX ;
62
+ private void GazeSmoother_OnSaccadeX ( ) => OnSaccadeX ? . Invoke ( ) ;
50
63
51
64
/// <inheritdoc />
65
+ [ Obsolete ( "Register for this provider's SaccadeProvider's actions instead" ) ]
52
66
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 ( ) ;
62
68
63
69
private static readonly List < InputDevice > InputDeviceList = new List < InputDevice > ( ) ;
64
70
private InputDevice eyeTrackingDevice = default ( InputDevice ) ;
@@ -85,14 +91,14 @@ private void ReadProfile()
85
91
{
86
92
if ( ConfigurationProfile == null )
87
93
{
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.") ;
89
95
return ;
90
96
}
91
97
92
98
MixedRealityEyeTrackingProfile profile = ConfigurationProfile as MixedRealityEyeTrackingProfile ;
93
99
if ( profile == null )
94
100
{
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.") ;
96
102
return ;
97
103
}
98
104
@@ -125,150 +131,28 @@ public override void Update()
125
131
}
126
132
}
127
133
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 ) ;
177
135
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 ) )
227
141
{
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 ) ;
245
144
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 ) ;
254
146
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 )
258
148
{
259
- PostOnSaccadeVertically ( ) ;
149
+ newGaze = gazeSmoother . SmoothGaze ( newGaze ) ;
260
150
}
261
151
262
- PostOnSaccade ( ) ;
263
-
264
- return true ;
152
+ Service ? . EyeGazeProvider ? . UpdateEyeGaze ( this , newGaze , DateTime . UtcNow ) ;
265
153
}
266
- return false ;
154
+ #endif // UNITY_OPENXR
267
155
}
268
156
}
269
-
270
- private void PostOnSaccade ( ) => OnSaccade ? . Invoke ( ) ;
271
- private void PostOnSaccadeHorizontally ( ) => OnSaccadeX ? . Invoke ( ) ;
272
- private void PostOnSaccadeVertically ( ) => OnSaccadeY ? . Invoke ( ) ;
273
157
}
274
158
}
0 commit comments