-
Notifications
You must be signed in to change notification settings - Fork 144
/
TobiiEyeXPlugin.cs
314 lines (247 loc) · 13.6 KB
/
TobiiEyeXPlugin.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
using System;
using System.Collections.Generic;
using EyeXFramework;
using FreePIE.Core.Contracts;
using SlimDX;
using Tobii.EyeX.Client;
using Tobii.EyeX.Framework;
namespace FreePIE.Core.Plugins
{
[GlobalType(Type = typeof(TobiiEyeXGlobal))]
public class TobiiEyeXPlugin : Plugin
{
private EyeXHost host;
private EyePositionDataStream eyeDataStream;
private GazePointDataStream gazeDataStream;
private Vector3 eyeOffsetMm;
private Vector3 eyeOffsetNorm;
private float targetRoll;
private float targetYaw;
private Vector3 targetAverageHeadPositionMm;
private Vector3 targetAverageHeadPositionNorm;
public float Yaw { get; private set; }
public float Roll { get; private set; }
// You can't calculate pitch using eye tracker at the moment.
public Vector3 AverageHeadPositionMm { get; private set; }
public Vector3 AverageHeadPositionNorm { get; private set; }
public EyePositionEventArgs LastEyePositionEventArgs { get; private set; }
public GazePointEventArgs LastGazePointEventArgs { get; private set; }
public Vector2 NormalizedGazePoint { get; private set; }
public Vector2 NormalizedCenterDelta { get; private set; }
public UpdateReason UpdateReason { get; private set; }
public UserPresence UserPresence { get; private set; }
public EyeTrackingDeviceStatus DeviceStatus { get; private set; }
public string CurrentProfileName { get; private set; }
public Vector2 DisplaySize { get; private set; }
public TrackStatus TrackStatus { get; private set; }
public override object CreateGlobal()
{
return new TobiiEyeXGlobal(this);
}
public override string FriendlyName
{
get { return "Tobii EyeX"; }
}
public override bool GetProperty(int index, IPluginProperty property)
{
return false;
}
public override bool SetProperties(Dictionary<string, object> properties)
{
return true;
}
public override Action Start()
{
host = new EyeXHost();
host.Start();
host.UserPresenceChanged += HostOnUserPresenceChanged;
host.EyeTrackingDeviceStatusChanged += HostOnEyeTrackingDeviceStatusChanged;
host.UserProfileNameChanged += HostOnUserProfileNameChanged;
host.DisplaySizeChanged += HostOnDisplaySizeChanged;
gazeDataStream = host.CreateGazePointDataStream(GazePointDataMode.Unfiltered);
eyeDataStream = host.CreateEyePositionDataStream();
gazeDataStream.Next += GazeDataStreamOnNext;
eyeDataStream.Next += EyeDataStreamOnNext;
return null;
}
public override void Stop()
{
gazeDataStream.Next -= GazeDataStreamOnNext;
eyeDataStream.Next -= EyeDataStreamOnNext;
gazeDataStream.Dispose();
eyeDataStream.Dispose();
host.DisplaySizeChanged -= HostOnDisplaySizeChanged;
host.UserProfileNameChanged -= HostOnUserProfileNameChanged;
host.EyeTrackingDeviceStatusChanged -= HostOnEyeTrackingDeviceStatusChanged;
host.UserPresenceChanged -= HostOnUserPresenceChanged;
host.Dispose();
}
public override void DoBeforeNextExecute()
{
}
private void HostOnDisplaySizeChanged(object sender, EngineStateValue<Size2> engineStateValue)
{
if(!engineStateValue.IsValid)
return;
DisplaySize = new Vector2((float)engineStateValue.Value.Width, (float)engineStateValue.Value.Height);
UpdateReason = UpdateReason.DisplaySizeChanged;
OnUpdate();
}
private void HostOnUserProfileNameChanged(object sender, EngineStateValue<string> engineStateValue)
{
if(!engineStateValue.IsValid)
return;
CurrentProfileName = engineStateValue.Value;
UpdateReason = UpdateReason.CurrentProfileNameChanged;
OnUpdate();
}
private void HostOnEyeTrackingDeviceStatusChanged(object sender, EngineStateValue<EyeTrackingDeviceStatus> engineStateValue)
{
if(!engineStateValue.IsValid)
return;
DeviceStatus = engineStateValue.Value;
UpdateReason = UpdateReason.DeviceStatusChanged;
OnUpdate();
}
private void HostOnUserPresenceChanged(object sender, EngineStateValue<UserPresence> engineStateValue)
{
if(!engineStateValue.IsValid)
return;
UserPresence = engineStateValue.Value;
UpdateReason = UpdateReason.UserPresenceChanged;
OnUpdate();
}
private void EyeDataStreamOnNext(object sender, EyePositionEventArgs eyePositionEventArgs)
{
LastEyePositionEventArgs = eyePositionEventArgs;
UpdateReason = UpdateReason.EyeDataChanged;
TrackStatus = TrackStatus.NoEyes;
var rightEyePositionMm = new Vector3((float)eyePositionEventArgs.RightEye.X, (float)eyePositionEventArgs.RightEye.Y, (float)eyePositionEventArgs.RightEye.Z);
var leftEyePositionMm = new Vector3((float)eyePositionEventArgs.LeftEye.X, (float)eyePositionEventArgs.LeftEye.Y, (float)eyePositionEventArgs.LeftEye.Z);
var rightEyePositionNorm = new Vector3((float)eyePositionEventArgs.RightEyeNormalized.X, (float)eyePositionEventArgs.RightEyeNormalized.Y, (float)eyePositionEventArgs.RightEyeNormalized.Z);
var leftEyePositionNorm = new Vector3((float)eyePositionEventArgs.LeftEyeNormalized.X, (float)eyePositionEventArgs.LeftEyeNormalized.Y, (float)eyePositionEventArgs.LeftEyeNormalized.Z);
if(eyePositionEventArgs.LeftEye.IsValid && eyePositionEventArgs.RightEye.IsValid)
{
TrackStatus = TrackStatus.BothEyes;
eyeOffsetMm = rightEyePositionMm - leftEyePositionMm;
eyeOffsetNorm = rightEyePositionNorm - leftEyePositionNorm;
}
else if(eyePositionEventArgs.LeftEye.IsValid)
{
TrackStatus = TrackStatus.OnlyLeftEye;
}
else if(eyePositionEventArgs.RightEye.IsValid)
{
TrackStatus = TrackStatus.OnlyRightEye;
}
switch(TrackStatus)
{
case TrackStatus.BothEyes:
targetAverageHeadPositionMm = (rightEyePositionMm + leftEyePositionMm) / 2f;
targetAverageHeadPositionNorm = (rightEyePositionNorm + leftEyePositionNorm) / 2f;
break;
case TrackStatus.OnlyLeftEye:
targetAverageHeadPositionMm = leftEyePositionMm + eyeOffsetMm / 2f;
targetAverageHeadPositionNorm = leftEyePositionNorm + eyeOffsetNorm / 2f;
break;
case TrackStatus.OnlyRightEye:
targetAverageHeadPositionMm = rightEyePositionMm - eyeOffsetMm / 2f;
targetAverageHeadPositionNorm = rightEyePositionNorm - eyeOffsetNorm / 2f;
break;
case TrackStatus.NoEyes:
default:
//Don't update D:
break;
}
targetRoll = (float)Math.Atan2(eyeOffsetMm.Y, eyeOffsetMm.X);
targetYaw = -(float)Math.Atan2(eyeOffsetMm.Z, eyeOffsetMm.X);
Roll = Lerp(Roll, targetRoll, 0.6f);
Yaw = Lerp(Yaw, targetYaw, 0.6f);
AverageHeadPositionMm = Lerp(AverageHeadPositionMm, targetAverageHeadPositionMm, 0.6f);
AverageHeadPositionNorm = Lerp(AverageHeadPositionNorm, targetAverageHeadPositionNorm, 0.6f);
OnUpdate();
}
private void GazeDataStreamOnNext(object sender, GazePointEventArgs gazePointEventArgs)
{
LastGazePointEventArgs = gazePointEventArgs;
const double screenExtensionFactor = 0;
var screenExtensionX = host.ScreenBounds.Value.Width * screenExtensionFactor;
var screenExtensionY = host.ScreenBounds.Value.Height * screenExtensionFactor;
var gazePointX = gazePointEventArgs.X + screenExtensionX / 2;
var gazePointY = gazePointEventArgs.Y + screenExtensionY / 2;
var screenWidth = host.ScreenBounds.Value.Width + screenExtensionX;
var screenHeight = host.ScreenBounds.Value.Height + screenExtensionY;
var normalizedGazePointX = (float)Math.Min(Math.Max((gazePointX / screenWidth), 0.0), 1.0);
var normalizedGazePointY = (float)Math.Min(Math.Max((gazePointY / screenHeight), 0.0), 1.0);
NormalizedGazePoint = new Vector2(normalizedGazePointX, normalizedGazePointY);
var normalizedDistanceFromCenterX = (normalizedGazePointX - 0.5f) * 2.0f;
var normalizedDistanceFromCenterY = (normalizedGazePointY - 0.5f) * 2.0f;
NormalizedCenterDelta = new Vector2(normalizedDistanceFromCenterX, normalizedDistanceFromCenterY);
UpdateReason = UpdateReason.GazeDataChanged;
OnUpdate();
}
private static float Lerp(float lower, float higher, float alpha)
{
return lower + (higher - lower) * alpha;
}
private static Vector3 Lerp(Vector3 lower, Vector3 higher, float alpha)
{
return lower + (higher - lower) * alpha;
}
}
[Global(Name = "tobiiEyeX")]
public class TobiiEyeXGlobal : UpdateblePluginGlobal<TobiiEyeXPlugin>
{
public TobiiEyeXGlobal(TobiiEyeXPlugin plugin) : base(plugin) { }
public float yaw { get { return plugin.Yaw; } }
public float roll { get { return plugin.Roll; } }
public float normalizedCenterDeltaX { get { return plugin.NormalizedCenterDelta.X; } }
public float normalizedCenterDeltaY { get { return plugin.NormalizedCenterDelta.Y; } }
public float gazePointNormalizedX { get { return plugin.NormalizedGazePoint.X; } }
public float gazePointNormalizedY { get { return plugin.NormalizedGazePoint.Y; } }
public float gazePointInPixelsX { get { return (float)plugin.LastGazePointEventArgs.X; } }
public float gazePointInPixelsY { get { return (float)plugin.LastGazePointEventArgs.Y; } }
public float gazeDataTimestamp { get { return (float)plugin.LastGazePointEventArgs.Timestamp; } }
public float leftEyePositionInMmX { get { return (float)plugin.LastEyePositionEventArgs.LeftEye.X; } }
public float leftEyePositionInMmY { get { return (float)plugin.LastEyePositionEventArgs.LeftEye.Y; } }
public float leftEyePositionInMmZ { get { return (float)plugin.LastEyePositionEventArgs.LeftEye.Z; } }
public float rightEyePositionInMmX { get { return (float)plugin.LastEyePositionEventArgs.RightEye.X; } }
public float rightEyePositionInMmY { get { return (float)plugin.LastEyePositionEventArgs.RightEye.Y; } }
public float rightEyePositionInMmZ { get { return (float)plugin.LastEyePositionEventArgs.RightEye.Z; } }
public float leftEyePositionNormalizedX { get { return (float)plugin.LastEyePositionEventArgs.LeftEyeNormalized.X; } }
public float leftEyePositionNormalizedY { get { return (float)plugin.LastEyePositionEventArgs.LeftEyeNormalized.Y; } }
public float leftEyePositionNormalizedZ { get { return (float)plugin.LastEyePositionEventArgs.LeftEyeNormalized.Z; } }
public float rightEyePositionNormalizedX { get { return (float)plugin.LastEyePositionEventArgs.RightEyeNormalized.X; } }
public float rightEyePositionNormalizedY { get { return (float)plugin.LastEyePositionEventArgs.RightEyeNormalized.Y; } }
public float rightEyePositionNormalizedZ { get { return (float)plugin.LastEyePositionEventArgs.RightEyeNormalized.Z; } }
public float averageEyePositionInMmX { get { return plugin.AverageHeadPositionMm.X; } }
public float averageEyePositionInMmY { get { return plugin.AverageHeadPositionMm.Y; } }
public float averageEyePositionInMmZ { get { return plugin.AverageHeadPositionMm.Z; } }
public float averageEyePositionNormalizedX { get { return plugin.AverageHeadPositionNorm.X; } }
public float averageEyePositionNormalizedY { get { return plugin.AverageHeadPositionNorm.Y; } }
public float averageEyePositionNormalizedZ { get { return plugin.AverageHeadPositionNorm.Z; } }
public float eyeDataTimestamp { get { return (float)plugin.LastEyePositionEventArgs.Timestamp; } }
public string updateReason { get { return plugin.UpdateReason.ToString(); } }
public string userPresence { get { return plugin.UserPresence.ToString(); } }
public string deviceStatus { get { return plugin.DeviceStatus.ToString(); } }
public string userProfileName { get { return plugin.CurrentProfileName; } }
public float displaySizeX { get { return plugin.DisplaySize.X; } }
public float displaySizeY { get { return plugin.DisplaySize.Y; } }
}
public enum UpdateReason
{
GazeDataChanged,
EyeDataChanged,
UserPresenceChanged,
DeviceStatusChanged,
CurrentProfileNameChanged,
DisplaySizeChanged
}
public enum TrackStatus
{
BothEyes,
OnlyLeftEye,
OnlyRightEye,
NoEyes
}
}